Touch location misinterpreted on iPad when rescaling an iframe showing a kineticjs canvas - html

I am building a widget using KineticJS (which is fantastic). It allows the user to drag images around in a canvas.
I can view and use the widget inside an iframe: this works perfectly on all the browsers and devices I have tried.
I can even scale that iframe so the page is responsive. Following another stackoverflow post, I have added the css:
#media screen and (max-width : 768px) {
iframe {
-moz-transform-origin: 0 0;
-o-transform-origin: 0 0;
-webkit-transform-origin: 0 0;
-moz-transform: scale(0.85);
-o-transform: scale(0.85);
-webkit-transform: scale(0.85);
}
}
This works well on my Mac.
But on an iPad, although the iframe appears properly scaled, when you try to drag elements around, it seems to think they are in their unscaled positions.
Is there a way I can get this to work on the iPad too? Or do I need to rethink my design (in which case, what design should I use)?
thanks!

Thanks to #irie11's suggestion, I have now implemented this. In the interests of posterity, here is my full approach.
I added some javascript to rescale the canvas when the window changes size:
function scaleCanvasToContainer() {
var container_width = $("#my-containers-parent-id").width();
var scale = 1.0;
if (container_width<target_width) {
scale = container_width/target_width;
}
$("#my-container-id").css("max-height",scale*target_height+"px");
stage.setScale(scale,scale);
stage.setWidth(scale*target_width);
stage.setHeight(scale*target_height);
stage.draw();
drawLines();
}
window.onresize = function(event) {
scaleCanvasToContainer();
}
...
scaleCanvasToContainer();
Note the above also resizes the canvas (i.e. changes its width and height), and for good measure sets the maximum height through css too.
I discovered that when you use context.moveTo() these coordinates are not scaled, so I had to manually scale them, e.g.
function drawLines() {
var canvas = layer.getCanvas();
var context = canvas.getContext();
var scaleX = stage.getScaleX();
var scaleY = stage.getScaleY();
context.beginPath();
context.moveTo(targetX*scaleX,targetY*scaleY);
...
}
I changed the css on my canvas a little to include:
background-size: contain;
background-repeat: no-repeat;
So far, we have scaled the content to the size of the containing iframe. However, we also need to make that iframe have the right size.
So I added some javascript to the calling page:
<script>
function scaleIFrame() {
var target_width = 850;
var container_width = $("#iframe-parent").width();
var width = target_width;
if (container_width<target_width) {
width = container_width;
}
$("#iframe-parent iframe").css("width",width+"px");
}
window.onresize = function(event) {
scaleIFrame();
}
scaleIFrame();
</script>
This leaves me with just one problem: setting the height of the iframe. In theory I should be able to access the height of the contents of the iframe using something like this (see this link):
var iframe = $("iframe");
var iframe_internal = iframe.contents().find("#my-content");
iframe.css("height",iframe_internal.style.height+"px");
This requires the two sites involved to be at the same domain; if they are on the same top-level domain you can still do it if you include a document.domain tag in both the iframe content and the calling page (see this SO post). You may also want to give your localhost the domain name you are using, so that you can test it (see this SO post) - I just added a new line to my /etc/hosts file: '127.0.0.1 local.example.com'.
I followed this last process (my two sites have the same top-level domain), but when I print out the iframe_internal variable it is an object with zero length, so something is going wrong here.
Still, I am very happy to have got this far. It works on the iPad and all browsers I have tested.

Related

Can't measure Clientwidth of Html-Image in Firefox

I am trying to place markers on points of interest (poi) on an Image.
These poi have been set in a different software and were stored in a database. The position is determined by their pixel position relative to the original Image. In my webapp the Images are scaled down thanks to panzoom.js (a plugin irrelevant to my question I think). I got the right formula to scale the markerposition, the only Problem is:
In firefox I'm unable to read the Images size in time (In Chrome that's not an Issue).
This is the Code
$(document).ready(function ()
{
var imagectrl = document.getElementById('<%= img.ClientID %>');
var hiddenfield = document.getElementById('<%= hf.ClientID %>');
if (hiddenfield.value == "")
{
var myWidth;
var myHeight;
myWidth = imagectrl.clientWidth;
myHeight = imagectrl.clientHeight;
hiddenfield.value = myWidth + ';' + myHeight;
__doPostBack();
}
});
If I do a postback manually (clicking a button that shows the Image in higher quality) the size gets written correctly.
I've also tried calling an identical function from Code behind when my X or Y are 0, but nothing worked.
What can i do to get the Images size when first loading the page?
Firefox has a different implementation on asynchronous operations like image loading than Chrome. I guess this could be the reason why in Chrome you can access the image right away with $(document).ready, but in Firefox the image source gets loaded after the document is ready - thus clientWidth and clientHeight will be undefined.
Solution: Define an onload event handler on your image and put your logic into that method:
$(document).ready(function ()
{
var imagectrl = document.getElementById('<%= img.ClientID %>');
var hiddenfield = document.getElementById('<%= hf.ClientID %>');
imagectrl.onload = function() {
if (hiddenfield.value == "")
{
var myWidth;
var myHeight;
myWidth = imagectrl.clientWidth;
myHeight = imagectrl.clientHeight;
hiddenfield.value = myWidth + ';' + myHeight;
__doPostBack();
}
}
});
I found a Solution:
No matter what I did, the Image itself can't be measured in time.
So i gave the Image the height of it's surrounding control via CSS and used
AddHandler dvGalerieFill.Load, AddressOf Me.measure_height
in the Page_Load method to react to the loading of the surrounding control.
In "measure_height" I called my Javascript function.
Through the height of the control (wich is the height of my image)
I can calculate the width of my image as height and width rescale with the same factor.

Google Maps InfoWindow width is overwritten even when set correctly

I'm using Google Maps API and I have some troubles about InfoWindow.
Here is a summary :
I'm loading the InfoWindow's content when user clicks on a marker
The content is a partial view, loaded thanks to an Ajax call
In the .done callback, I call an asynchronous method which will insert data into the InfoWindow content. I need to do this because I want the InfoWindow main content to be displayed immediately, whereas this "bonus" information could be displayed after some tenths of seconds.
This perfectly works ; but I have a white strip on the right of my InfoWindow I can't remove (see the picture below)
However, the content I load is included in a <div> with a fixed width :
<div id="div-main-infoWindow">
<!-- All my content -->
</div>
And in my CSS, I wrote :
#div-main-infoWindow {
width:342px !important;
}
The loading of the InfoWindow, with more details, looks like this :
$.ajax({
url : "/my-url",
async : false,
contentType : "application/json; charset=utf-8",
dataType : "json",
type : "POST",
data : JSON.stringify(myModel)
}).done(function(response) {
MarkerContent = response.Content; //The HTML content to display
MyAsyncMethod().then(function(response) {
//do some formatting with response
return result; //result is just a small HTML string
}).done(function(result1, result2) {
//do some formatting with result1 and result2
$("#myNode").html(/*formatted result*/);
})
//info is a global variable defined as new google.maps.InfoWindow()
info.setOptions({
content: MarkerContent,
maxWidth: 342 //To be sure
});
info.open(map, marker);
});
});
The content is perfectly OK, the problem is all about this white strip.
Looking at my browser console (I reproduced it in ALL browsers), I can see this :
As you can see there, my <div> containing all my data loaded from my ajax call is OK with the good size (green rectangle, corresponding to the greyed zone in the screenshot), BUT the above divs (from Google API itself, into the red rectangles) have a bigger size, from which the problem is.
The only way I found is running this jQuery script modifying the InfoWindow internal structure :
$(".gm-style-iw").next().remove();
$(".gm-style-iw").prev().children().last().width(342);
$(".gm-style-iw").prev().children(":nth-child(2)").width(342);
$(".gm-style-iw").width(342);
$(".gm-style-iw").children().first().css("overflow", "hidden");
$(".gm-style-iw").children().first().children().first().css("overflow", "hidden");
$(".gm-style-iw").parent().width(342);
Note : gm-style-iw is the class name given by Google of the div containing all the content of the InfoWindow, the one hovered on the above screenshot. I also add this rule in my CSS :
.gm-style-iw {
width: 342px !important; //also tried with 100% !important, not better
}
It works in the console, however, it has no effect when written in the code itself, in the .done callback, or in the domready Google Maps' event...
However, in this late case, if I encapsulate the above jQuery script in a setTimeout(), it works !! I commented the asynchronous method, so it's not this one which is guilty, but it seems domready is executed whereas 100% of the InfoWindow is not still displayed - which is contrary to the doc.
I also tried to move my info.setOptions outside the .done callback and put it at after it, no effect.
So, how can I display a "normal" InfoWindow without this white strip on the right ?
I don't want to implement InfoBubble or other custom InfoWindow library. It's a personal project and I want to understand why and where the problem is. And of course, find a solution.
Thank you for your help !
It's a little bit more complex than you think.
Just some things:
did you notice that there is a close-button? Even when you remove the button the space will be there, because the API calculates the size of the other nodes based on this space
the tip must be in the center
there are additional containers for rounded borders and shadows
the size of the infoWindow will be calculated so that it fits into the viewport
the content must be scrollable when needed
the position of the infowindow must be set(therefore it's required to calculate the exact height of the infowindow)
Basically: all these things require to calculate exact sizes and positions, most of the nodes in the infowindow are absolute positioned, it's rather a technique like it will be used in DTP than you would use it in a HTML-document.
Additionally: the InfoWindows will be modified very often to fix bugs, a solution which works today may be broken tomorrow.
However, an approach which currently works for me:
set the maxWidth of the infowindow to the desired width - 51 (would be 291 in this case)
It's not possible to apply !important-rules via $.css , so it must be done via a stylesheet directly. To be able to access all the elements set a new class for the root-node of the infowindow(note that there may be infoWindows which you can't control, e.g. for POI's):
google.maps.event.addListener(infowindow,'domready',function(){
$('#div-main-infoWindow')//the root of the content
.closest('.gm-style-iw')
.parent().addClass('custom-iw');
});
the CSS:
.custom-iw .gm-style-iw {
top:15px !important;
left:0 !important;
border-radius:2px;
}
.custom-iw>div:first-child>div:nth-child(2) {
display:none;
}
/** the shadow **/
.custom-iw>div:first-child>div:last-child {
left:0 !important;
top:0px;
box-shadow: rgba(0, 0, 0, 0.6) 0px 1px 6px;
z-index:-1 !important;
}
.custom-iw .gm-style-iw,
.custom-iw .gm-style-iw>div,
.custom-iw .gm-style-iw>div>div {
width:100% !important;
max-width:100% !important;
}
/** set here the width **/
.custom-iw,
.custom-iw>div:first-child>div:last-child {
width:342px !important;
}
/** set here the desired background-color **/
#div-main-infoWindow,
.custom-iw>div:first-child>div:nth-child(n-1)>div>div,
.custom-iw>div>div:last-child,
.custom-iw .gm-style-iw,
.custom-iw .gm-style-iw>div,
.custom-iw .gm-style-iw>div>div {
background-color:orange !important;
}
/** close-button(note that there may be a scrollbar) **/
.custom-iw>div:last-child {
top:1px !important;
right:0 !important;
}
/** padding of the content **/
#div-main-infoWindow {
padding:6px;
}
Demo(as I said, may be broken tomorrow): http://jsfiddle.net/doktormolle/k57qojq7/
Bit late to the party here, but after searching for ages to achieve the same thing as the OP a thought occurred to me. The width seemed to be getting set by the parent element of .gm-style-iw so I just set the parent elements width to auto using jQuery and hey presto! So here is my code in the hope it may help someone in the future.
JS
$('.gm-style-iw').parent().css('width', 'auto');
CSS
.gm-style-iw {
width: auto !important;
top: 0 !important;
left: 0 !important;
}

Iframe Scrolling & height not working properly in iPad

after, Lot of struggle and Research at finally, I got the solution to IFrame scrolling issue in iPad browsers.
Solution:
$(function() {
if (/iPhone|iPod|iPad/.test(navigator.userAgent)) {
$("iframe").bind("load",function() {
var b = this.contentWindow.document.body;
var div = $(document.createElement("div"));
var divCSS = {
'height' : $(this.parentNode).height(),
'width' : $(this.parentNode).width(),
'overflow' : 'scroll'
}
div.css(divCSS);
// Move the body's children into this wrapper
while (b.firstChild)
{
div.append(b.firstChild);
}
// Append the wrapper to the body
$(b).append(div);
});
}
})
Its working fine randomly.Here I am facing two issues are:
Iframe Scrolling working randomly means its not working every time I refresh.
The same problem "#1", I am having while rotating the device "landscape" to "portrait" & reverse case.

Chart.js canvas resize

In (Android WebView HTML5 canvas error) i posted a question regarding plotting graphs using Graph.js library.
The problem i have now is that if i call the function to plot the graph multiple times, the canvas resizes every time. Each time the graph is redrawn to the same canvas, its size also changes.
I also tried setting the size of the canvas but without success.
What could be the reason? Why does the canvas resize every time?
I had a lot of problems with that, because after all of that my line graphic looked terrible when mouse hovering and I found a simpler way to do it, hope it will help :)
Use these Chart.js options:
// Boolean - whether or not the chart should be responsive and resize when the browser does.
responsive: true,
// Boolean - whether to maintain the starting aspect ratio or not when responsive, if set to false, will take up entire container
maintainAspectRatio: false,
What's happening is Chart.js multiplies the size of the canvas when it is called then attempts to scale it back down using CSS, the purpose being to provide higher resolution graphs for high-dpi devices.
The problem is it doesn't realize it has already done this, so when called successive times, it multiplies the already (doubled or whatever) size AGAIN until things start to break. (What's actually happening is it is checking whether it should add more pixels to the canvas by changing the DOM attribute for width and height, if it should, multiplying it by some factor, usually 2, then changing that, and then changing the css style attribute to maintain the same size on the page.)
For example, when you run it once and your canvas width and height are set to 300, it sets them to 600, then changes the style attribute to 300... but if you run it again, it sees that the DOM width and height are 600 (check the other answer to this question to see why) and then sets it to 1200 and the css width and height to 600.
Not the most elegant solution, but I solved this problem while maintaining the enhanced resolution for retina devices by simply setting the width and height of the canvas manually before each successive call to Chart.js
var ctx = document.getElementById("canvas").getContext("2d");
ctx.canvas.width = 300;
ctx.canvas.height = 300;
var myDoughnut = new Chart(ctx).Doughnut(doughnutData);
This works for me:
<body>
<form>
[...]
<div style="position:absolute; top:60px; left:10px; width:500px; height:500px;">
<canvas id="cv_values"></canvas>
<script type="text/javascript">
var indicatedValueData = {
labels: ["1", "2", "3"],
datasets: [
{
[...]
};
var cv_values = document.getElementById("cv_values").getContext("2d");
var myChart = new Chart(cv_values, { type: "line", data: indicatedValueData });
</script>
</div>
</form>
</body>
The essential fact is that we have to set the size of the canvas in the div-tag.
In IOS and Android the browser hides the toolbar when you are scrolling, thereby changing the size of the window which inturn lead chartjs to resize the graph.
The solution is to maintain the aspect ratio.
var options = {
responsive: true,
maintainAspectRatio: true
}
This should solve your problem.
I had to use a combination of multiple answers here with some minor tweaks.
First, it is necessary that you wrap the canvas element within a block-level container. I say to you, do not let the canvas element have any siblings; let it be a lonely child, for it is stubborn and spoiled. (The wrapper may not need any sizing restrictions placed on it, but for safety it may be good to have a max-height applied to it.)
After assuring that the previous conditions are met, when initiating the chart, make sure the following options are used:
var options = {
"responsive": true,
"maintainAspectRatio": false
}
If you want to adjust the height of the chart, do so at the canvas element level.
<canvas height="500"></canvas>
Do not try to deal with the child in any other manner. This should result in a satisfyingly, properly laid-out chart, one that stays in its crib peacefully.
As jcmiller11 suggested, setting the width and height helps.
A slightly nicer solution is to retrieve the width and height of the canvas before drawing the chart.
Then using those numbers for setting the chart on each subsequent re-draw of the chart.
This makes sure there are no constants in the javascript code.
ctx.canvas.originalwidth = ctx.canvas.width;
ctx.canvas.originalheight = ctx.canvas.height;
function drawchart() {
ctx.canvas.width = ctx.canvas.originalwidth;
ctx.canvas.height = ctx.canvas.originalheight;
var chartctx = new Chart(ctx);
myNewBarChart = chartctx.Bar(data, chartSettings);
}
I had a similar problem and found your answer.. I eventually came to a solution.
It looks like the source of Chart.js has the following(presumably because it is not supposed to re-render and entirely different graph in the same canvas):
//High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale.
if (window.devicePixelRatio) {
context.canvas.style.width = width + "px";
context.canvas.style.height = height + "px";
context.canvas.height = height * window.devicePixelRatio;
context.canvas.width = width * window.devicePixelRatio;
context.scale(window.devicePixelRatio, window.devicePixelRatio);
}
This is fine if it is called once, but when you redraw multiple times you end up changing the size of the canvas DOM element multiple times causing re-size.
Hope that helps!
I was having the same problem. I was able to solve it by setting option:
responsive: false,
maintainAspectRatio: true,
showScale: false,
And in css, set the width of the container div the same as the canvas:
#canvasContainer {
width: 300px;
}
canvas {
width: 300px;
}
If anyone is having problems, I found a solution that doesn't involve sacrificing responsiveness etc.
Simply wrap your canvas in a div container (no styling) and reset the contents of the div to an empty canvas with ID before calling the Chart constructor.
Example:
HTML:
<div id="chartContainer">
<canvas id="myChart"></canvas>
</div>
JS:
$("#chartContainer").html('<canvas id="myChart"></canvas>');
//call new Chart() as usual
I tried to Resize Canvas using jQuery but it din't work well. I think CSS3 is the best option you can try on, if you want on hover zooming at certain level.
Following hover option from other codepan link:
.style_prevu_kit:hover{
z-index: 2;
-webkit-transition: all 200ms ease-in;
-webkit-transform: scale(1.5);
-ms-transition: all 200ms ease-in;
-ms-transform: scale(1.5);
-moz-transition: all 200ms ease-in;
-moz-transform: scale(1.5);
transition: all 200ms ease-in;
transform: scale(1.5);
}
Follow my codepan link:
https://codepen.io/hitman0775/pen/XZZzqN
Here is the official dokumentation: https://www.chartjs.org/docs/latest/general/responsive.html
Detecting when the canvas size changes can not be done directly from the canvas element. Chart.js uses its parent container to update the canvas render and display sizes. However, this method requires the container to be relatively positioned and dedicated to the chart canvas only. Responsiveness can then be achieved by setting relative values for the container size (example):
<div class="chart-container" style="position: relative; height:40vh; width:80vw">
<canvas id="chart"></canvas>
</div>
I had the same kind of scaling issue's using Angular CLI. Was able to get it working by removing this line from the index.html:
<script src="node_modules/chart.js/dist/Chart.bundle.min.js"></script>
and then in the angular-cli.json file, in the scripts section, using this:
"scripts": ["../node_modules/chart.js/dist/Chart.bundle.min.js"]
Source: mikebarr58
I tried multiple answers on this thread and what worked for me was that (Note I am using reactjs), checking my previous props passed into the chart component.
When I was resizing my DOM, the chart was getting re-drawn with empty data.
componentDidUpdate(prevProps: ChartProps) { if(prevProps.data !== props.data) renderChart(); }
The accepted - responsive:true, maintainAspectRatio:false - did not work for my scenario.
But I found we can simply call the update method on the chart.
So I found myself tweaking values inside matchMedia listeners .. like so:
myRadarChart.options.scales.r.pointLabels.font.size = "1rem";
myRadarChart.update();
For me it is working with max-height:none (chart.js v3.9.1 and ng2-charts v3.1.2):
<div *ngIf="shartData">
<canvas style="max-height:none" baseChart [type]="'pie'"
[data]="shartData"
[options]="chartOptions">
</canvas>
</div>
Without max-height the chart height becomes "1".
The same is relevant when using chart.js directly without ng2-charts:
<div *ngIf="shartData">
<canvas style="max-height:none" id="chartID">
</canvas>
</div>
let ctx = <HTMLCanvasElement>document.getElementById("chartID");
let chart = new Chart(ctx, {
type: 'pie',
data: shartData,
options: chartOptions
});
Add div and it will solve the problem
<div style="position:absolute; top:50px; left:10px; width:500px; height:500px;"></div>
let canvasBox = ReactDOM.findDOMNode(this.refs.canvasBox);
let width = canvasBox.clientWidth;
let height = canvasBox.clientHeight;
let charts = ReactDOM.findDOMNode(this.refs.charts);
let ctx = charts.getContext('2d');
ctx.canvas.width = width;
ctx.canvas.height = height;
this.myChart = new Chart(ctx);

Crop image to canvas

I have 2 divs
<div id="image-orig">
<img src="image_example.jpg"/>
</div>
<div id="image-crop">
<canvas id="preview" style="width:548px;height:387px"></canvas>
</div>
image_example.jpg can be image any size.
function updatePreview(c) {
if(parseInt(c.w) > 0) {
var orig = $("#image-orig img")[0];
var canvas = $("#image-crop canvas")[0];
var context = canvas.getContext("2d");
context.drawImage(orig,
c.x*coeff,c.y*coeff,c.w*coeff,c.h*coeff,
0,0,canvas.width,canvas.height
);
}
}
$(function(){
$('#image-orig img').Jcrop({
onSelect: updatePreview,
onChange: updatePreview,
aspectRatio : parseFloat($('#image-orig img').width()/$('#image-orig img').height())
});
});
coeff - it's coefficient if size image larger div preview.
That's problem:
http://dbwap.ru/3725988.png
In second div (canvas). Quality image very low.
SOLUTION IS FOUND
canvas.width = c.w*coeff;
canvas.height = c.h*coeff;
context.drawImage(orig,
c.x*coeff,c.y*coeff,c.w*coeff,c.h*coeff,
0,0,c.w*coeff,c.h*coeff
);
$(that.el).find("#ImageCode").attr('src', canvas.toDataURL());
$(that.el).find("#ImageCode").show();
I'm just creating image tag and copying from canvas to image.
If you have access to .net, you can modify the way your new images are saved with JCrop:
http://mironabramson.com/blog/post/2009/04/Cropping-image-using-jQuery,-Jcrop-and-ASPNET.aspx
Solution available to you without using server-side (.net / php):
First, make sure that when you use JCrop that you have html5 canvas image smoothing enabled:
http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html
If that is already set, or has no effect, then I think your only other options to investigate image option available to you through each browser:
Enable smoothing in Mozilla - See this article as an example (look for 'mozImageSmoothingEnabled'):https://developer.mozilla.org/en/Canvas_tutorial/Using_images#Controlling_image_scaling_behavior
Apply filters in IE: http://code.flickr.com/blog/2008/11/12/on-ui-quality-the-little-things-client-side-image-resizing/
Note: There may be some sort of Flash solution that could work but it would probably be too difficult to combine any Flash solution with JCrop and html5 canvas.