Hammerjs v2.0.2 pinch to zoom image - javascript

I'm trying to create a pinch to zoom action in a Web app developed with Cordova for iOS and Android.
I've tried this script with Hammerjs 1.1.3, and in a first moment i think that was the version of Hammerjs to create this problem, so i'have "translated" the script to working with Hammerjs 2.0.2 but i have the same result either way.
In iOS (at the moment i'm using the Xcode emulator on my Mac) i'm able to zoom the image but after the first drag event the image is cut, and after this i can't drag the image.
I think this issue is caused by the css transform3d animation, have you ever had the same problem or something similar in iOS?
The HTML structure:
<div id="pinchzoom">
<img id="rect" src="..." width="2835" height="4289" alt="" />
</div>
This is the script in Hammerjs 2.0.2.
var elem = document.getElementById('pinchzoom');
var mc = Hammer(elem,{touchAction: 'manipulation'});
mc.get('pinch').set({enable: true});
var rect = document.getElementById('img_hover');
var posX=0, posY=0,
scale=1, last_scale,
last_posX=0, last_posY=0,
max_pos_x=0, max_pos_y=0;
mc.on('pan pinchout pinchin panend', function(ev){
switch(ev.type)
{
case 'pan':
if(scale != 1){
posX = last_posX + ev.deltaX;
posY = last_posY + ev.deltaY;
if(posX > max_pos_x){
posX = max_pos_x;
}
if(posX < -max_pos_x){
posX = -max_pos_x;
}
if(posY > max_pos_y){
posY = max_pos_y;
}
if(posY < -max_pos_y){
posY = -max_pos_y;
}
}else{
posX = 0;
posY = 0;
saved_posX = 0;
saved_posY = 0;
}
break;
case 'pinchin':
case 'pinchout':
last_scale = scale;
scale = Math.max(1, Math.min(last_scale * ev.scale, 10));
max_pos_x = Math.ceil((scale - 1) * rect.clientWidth / 2);
max_pos_y = Math.ceil((scale - 1) * rect.clientHeight / 2);
if(posX > max_pos_x){
posX = max_pos_x;
}
if(posX < -max_pos_x){
posX = -max_pos_x;
}
if(posY > max_pos_y){
posY = max_pos_y;
}
if(posY < -max_pos_y){
posY = -max_pos_y;
}
break;
case 'panend':
last_posX = posX < max_pos_x ? posX: max_pos_x;
last_posY = posY < max_pos_y ? posY: max_pos_y;
break;
}
// transform!
var transform =
"translate3d(0, 0, 0) " +
"scale3d(1, 1, 0) ";
if(scale != 1){
transform =
"translate3d("+posX+"px,"+posY+"px, 0) " +
"scale3d("+scale+","+scale+", 0) ";
}
rect.style.transform = transform;
rect.style.oTransform = transform;
rect.style.msTransform = transform;
rect.style.mozTransform = transform;
rect.style.webkitTransform = transform;
});

I Have Used jr-crop Plugin With Cordova Camera Plugin in Android & iOS.
Pinch To Zoom functionality Available in these plugin.

Related

How to set zoom focus based on pinch location using hammer.js

I am able to perform pinch for div content when passing the element to the method below, but whilst the pinch is performing the focus is always at the center.
How can I change the focus based on pinch location?
function hammerIt(elm) {
hammertime = new Hammer(elm, {});
hammertime.get('pinch').set({
enable: true
});
var posX = 0,
posY = 0,
scale = 1,
last_scale = 1,
last_posX = 0,
last_posY = 0,
max_pos_x = 0,
max_pos_y = 0,
transform = "",
el = elm;
hammertime.on('doubletap pan pinch panend pinchend', function(ev) {
if (ev.type == "doubletap") {
transform =
"translate3d(0, 0, 0) " +
"scale3d(2, 2, 1) ";
scale = 2;
last_scale = 2;
try {
if (window.getComputedStyle(el, null).getPropertyValue('-webkit-transform').toString() != "matrix(1, 0, 0, 1, 0, 0)") {
transform =
"translate3d(0, 0, 0) " +
"scale3d(1, 1, 1) ";
scale = 1;
last_scale = 1;
}
} catch (err) {}
el.style.webkitTransform = transform;
transform = "";
}
//pan
if (scale != 1) {
posX = last_posX + ev.deltaX;
posY = last_posY + ev.deltaY;
max_pos_x = Math.ceil((scale - 1) * el.clientWidth / 2);
max_pos_y = Math.ceil((scale - 1) * el.clientHeight / 2);
if (posX > max_pos_x) {
posX = max_pos_x;
}
if (posX < -max_pos_x) {
posX = -max_pos_x;
}
if (posY > max_pos_y) {
posY = max_pos_y;
}
if (posY < -max_pos_y) {
posY = -max_pos_y;
}
}
//pinch
if (ev.type == "pinch") {
scale = Math.max(.999, Math.min(last_scale * (ev.scale), 4));
}
if(ev.type == "pinchend"){last_scale = scale;}
//panend
if(ev.type == "panend"){
last_posX = posX < max_pos_x ? posX : max_pos_x;
last_posY = posY < max_pos_y ? posY : max_pos_y;
}
if (scale != 1) {
transform =
"translate3d(" + posX + "px," + posY + "px, 0) " +
"scale3d(" + scale + ", " + scale + ", 1)";
}
if (transform) {
el.style.webkitTransform = transform;
}
});
}

How to bound image pan when zooming (HTML Canvas)

I'm trying to limit boundaries and I'm running into issues. I'm upscaling an image from another canvas and then implementing zoom and pan. My issue (code below) is with limiting/capping the offsetx/y so that you never see the whitespace; only parts of the image.
Pardon the mess! Any help is appreciated! :P
var zoomIntensity = 0.2;
var canvas = document.getElementById("canvas");
var canvas2 = document.getElementById("canvas2");
var context = canvas.getContext("2d");
var context2 = canvas2.getContext("2d");
var width = 200;
var height = 200;
var scale = 1;
var originx = 0;
var originy = 0;
var offset = {x:0, y:0};
//fill smaller canvas with random pixels
for(var x = 0; x < 100; x++)
for(var y = 0; y < 100; y++)
{
var rando = function(){return Math.floor(Math.random() * 9)};
var val = rando();
context2.fillStyle = "#" + val + val + val;
context2.fillRect(x,y,1,1);
}
//draw the larger canvas
function draw()
{
context.imageSmoothingEnabled = false;
// Clear screen to white.
context.fillStyle = "white";
context.fillRect(originx - offset.x, originy - offset.y, width/scale, height/scale);
context.drawImage(canvas2, 0,0, width, height);
}
// Draw loop at 60FPS.
setInterval(draw, 1000/60);
canvas.onmousewheel = function (event){
event.preventDefault();
// Get mouse offset.
var mousex = event.clientX - canvas.offsetLeft;
var mousey = event.clientY - canvas.offsetTop;
// Normalize wheel to +1 or -1.
var wheel = event.wheelDelta/120;
// Compute zoom factor.
var zoom = Math.exp(wheel*zoomIntensity);
// Translate so the visible origin is at the context's origin.
context.translate(originx - offset.x, originy - offset.y); //offset is panning
//make sure we don't zoom out further than normal scale
var resultingScale = scale * zoom;
if(resultingScale < 1)
zoom = 1/scale;
// Compute the new visible origin. Originally the mouse is at a
// distance mouse/scale from the corner, we want the point under
// the mouse to remain in the same place after the zoom, but this
// is at mouse/new_scale away from the corner. Therefore we need to
// shift the origin (coordinates of the corner) to account for this.
originx -= mousex/(scale*zoom) - mousex/scale;
originy -= mousey/(scale*zoom) - mousey/scale;
// Scale it (centered around the origin due to the trasnslate above).
context.scale(zoom, zoom);
// Offset the visible origin to it's proper position.
context.translate(-originx + offset.x, -originy + offset.y); //offset is panning
// Update scale and others.
scale *= zoom;
}
document.onkeydown = function (evt)
{
var offsetx = 0;
var offsety = 0;
switch(evt.which)
{
case 37: //left
offsetx = 1;
break;
case 38: //up
offsety = 1;
break;
case 39: //right
offsetx = -1
break;
case 40: //down
offsety = -1;
break;
}
offsetx /= scale;
offsety /= scale;
offset.x += offsetx;
offset.y += offsety;
context.translate(offsetx,offsety);
}
<canvas id="canvas" width="200" height="200"></canvas>
<canvas id="canvas2" width="100" height="100"></canvas>
Using transformation matrix to constrain a view
To constrain the position you need to transform the corner coordinates of the image to screen coordinates. As getting the transform is still not standard across browsers the demo below holds a copy of the transform.
The object view holds the canvas view. When you use the function view.setBounds(top,left,right,bottom); the view will be locked to that area (the image you are viewing 0,0,100,100)
The scale and position (origin) will be constrained to keep the bounds outside or one the edge of the canvas context set by view.setContext(context).
The function scaleAt(pos,amount); will scale at a specified pos (eg mouse position)
To set the transform use view.apply() this will update the view transform and set the context transform.
The are a few other functions that may prove handy see code.
Demo uses mouse click drag to pan and wheel to zoom.
Demo is a copy of the OP's example width modifications to answer question.
// use requestAnimationFrame when doing any form of animation via javascript
requestAnimationFrame(draw);
var zoomIntensity = 0.2;
var canvas = document.getElementById("canvas");
var canvas2 = document.getElementById("canvas2");
var context = canvas.getContext("2d");
var context2 = canvas2.getContext("2d");
var width = 200;
var height = 200;
context.font = "24px arial";
context.textAlign = "center";
context.lineJoin = "round"; // to prevent miter spurs on strokeText
//fill smaller canvas with random pixels
for(var x = 0; x < 100; x++){
for(var y = 0; y < 100; y++) {
var rando = function(){return Math.floor(Math.random() * 9)};
var val = rando();
if(x === 0 || y === 0 || x === 99 || y === 99){
context2.fillStyle = "#FF0000";
}else{
context2.fillStyle = "#" + val + val + val;
}
context2.fillRect(x,y,1,1);
}
}
// mouse holds mouse position button state, and if mouse over canvas with overid
var mouse = {
pos : {x : 0, y : 0},
worldPos : {x : 0, y : 0},
posLast : {x : 0, y : 0},
button : false,
overId : "", // id of element mouse is over
dragging : false,
whichWheel : -1, // first wheel event will get the wheel
wheel : 0,
}
// View handles zoom and pan (can also handle rotate but have taken that out as rotate can not be contrained without losing some of the image or seeing some of the background.
const view = (()=>{
const matrix = [1,0,0,1,0,0]; // current view transform
const invMatrix = [1,0,0,1,0,0]; // current inverse view transform
var m = matrix; // alias
var im = invMatrix; // alias
var scale = 1; // current scale
const bounds = {
topLeft : 0,
left : 0,
right : 200,
bottom : 200,
}
var useConstraint = true; // if true then limit pan and zoom to
// keep bounds within the current context
var maxScale = 1;
const workPoint1 = {x :0, y : 0};
const workPoint2 = {x :0, y : 0};
const wp1 = workPoint1; // alias
const wp2 = workPoint2; // alias
var ctx;
const pos = { // current position of origin
x : 0,
y : 0,
}
var dirty = true;
const API = {
canvasDefault () { ctx.setTransform(1,0,0,1,0,0) },
apply(){
if(dirty){ this.update() }
ctx.setTransform(m[0],m[1],m[2],m[3],m[4],m[5]);
},
getScale () { return scale },
getMaxScale () { return maxScale },
matrix, // expose the matrix
invMatrix, // expose the inverse matrix
update(){ // call to update transforms
dirty = false;
m[3] = m[0] = scale;
m[1] = m[2] = 0;
m[4] = pos.x;
m[5] = pos.y;
if(useConstraint){
this.constrain();
}
this.invScale = 1 / scale;
// calculate the inverse transformation
var cross = m[0] * m[3] - m[1] * m[2];
im[0] = m[3] / cross;
im[1] = -m[1] / cross;
im[2] = -m[2] / cross;
im[3] = m[0] / cross;
},
constrain(){
maxScale = Math.min(
ctx.canvas.width / (bounds.right - bounds.left) ,
ctx.canvas.height / (bounds.bottom - bounds.top)
);
if (scale < maxScale) { m[0] = m[3] = scale = maxScale }
wp1.x = bounds.left;
wp1.y = bounds.top;
this.toScreen(wp1,wp2);
if (wp2.x > 0) { m[4] = pos.x -= wp2.x }
if (wp2.y > 0) { m[5] = pos.y -= wp2.y }
wp1.x = bounds.right;
wp1.y = bounds.bottom;
this.toScreen(wp1,wp2);
if (wp2.x < ctx.canvas.width) { m[4] = (pos.x -= wp2.x - ctx.canvas.width) }
if (wp2.y < ctx.canvas.height) { m[5] = (pos.y -= wp2.y - ctx.canvas.height) }
},
toWorld(from,point = {}){ // convert screen to world coords
var xx, yy;
if(dirty){ this.update() }
xx = from.x - m[4];
yy = from.y - m[5];
point.x = xx * im[0] + yy * im[2];
point.y = xx * im[1] + yy * im[3];
return point;
},
toScreen(from,point = {}){ // convert world coords to screen coords
if(dirty){ this.update() }
point.x = from.x * m[0] + from.y * m[2] + m[4];
point.y = from.x * m[1] + from.y * m[3] + m[5];
return point;
},
scaleAt(at, amount){ // at in screen coords
if(dirty){ this.update() }
scale *= amount;
pos.x = at.x - (at.x - pos.x) * amount;
pos.y = at.y - (at.y - pos.y) * amount;
dirty = true;
},
move(x,y){ // move is in screen coords
pos.x += x;
pos.y += y;
dirty = true;
},
setContext(context){
ctx = context;
dirty = true;
},
setBounds(top,left,right,bottom){
bounds.top = top;
bounds.left = left;
bounds.right = right;
bounds.bottom = bottom;
useConstraint = true;
dirty = true;
}
};
return API;
})();
view.setBounds(0,0,canvas2.width,canvas2.height);
view.setContext(context);
//draw the larger canvas
function draw(){
view.canvasDefault(); // se default transform to clear screen
context.imageSmoothingEnabled = false;
context.fillStyle = "white";
context.fillRect(0, 0, width, height);
view.apply(); // set the current view
context.drawImage(canvas2, 0,0);
view.canvasDefault();
if(view.getScale() === view.getMaxScale()){
context.fillStyle = "black";
context.strokeStyle = "white";
context.lineWidth = 2.5;
context.strokeText("Max scale.",context.canvas.width / 2,24);
context.fillText("Max scale.",context.canvas.width / 2,24);
}
requestAnimationFrame(draw);
if(mouse.overId === "canvas"){
canvas.style.cursor = mouse.button ? "none" : "move";
}else{
canvas.style.cursor = "default";
}
}
// add events to document so that mouse is captured when down on canvas
// This allows the mouseup event to be heard no matter where the mouse has
// moved to.
"mousemove,mousedown,mouseup,mousewheel,wheel,DOMMouseScroll".split(",")
.forEach(eventName=>document.addEventListener(eventName,mouseEvent));
function mouseEvent (event){
mouse.overId = event.target.id;
if(event.target.id === "canvas" || mouse.dragging){ // only interested in canvas mouse events including drag event started on the canvas.
mouse.posLast.x = mouse.pos.x;
mouse.posLast.y = mouse.pos.y;
mouse.pos.x = event.clientX - canvas.offsetLeft;
mouse.pos.y = event.clientY - canvas.offsetTop;
view.toWorld(mouse.pos, mouse.worldPos); // gets the world coords (where on canvas 2 the mouse is)
if (event.type === "mousemove"){
if(mouse.button){
view.move(
mouse.pos.x - mouse.posLast.x,
mouse.pos.y - mouse.posLast.y
)
}
} else if (event.type === "mousedown") { mouse.button = true; mouse.dragging = true }
else if (event.type === "mouseup") { mouse.button = false; mouse.dragging = false }
else if(event.type === "mousewheel" && (mouse.whichWheel === 1 || mouse.whichWheel === -1)){
mouse.whichWheel = 1;
mouse.wheel = event.wheelDelta;
}else if(event.type === "wheel" && (mouse.whichWheel === 2 || mouse.whichWheel === -1)){
mouse.whichWheel = 2;
mouse.wheel = -event.deltaY;
}else if(event.type === "DOMMouseScroll" && (mouse.whichWheel === 3 || mouse.whichWheel === -1)){
mouse.whichWheel = 3;
mouse.wheel = -event.detail;
}
if(mouse.wheel !== 0){
event.preventDefault();
view.scaleAt(mouse.pos, Math.exp((mouse.wheel / 120) *zoomIntensity));
mouse.wheel = 0;
}
}
}
div { user-select: none;} /* mouse prevent drag selecting content */
canvas { border:2px solid black;}
<div>
<canvas id="canvas" width="200" height="200"></canvas>
<canvas id="canvas2" width="100" height="100"></canvas>
<p>Mouse wheel to zoom. Mouse click drag to pan.</p>
<p>Zoomed image constrained to canvas</p>
</div>

Position DIV at cursor but within viewable area

I'm using the showDiv function below to display a DIV popup menu at the cursor position but I can't figure out how to tweak it so that the menu doesn't disappear off the bottom or right-hand edge of the viewable area, is it possible to compensate for this before displaying the DIV?
var posx;
var posy;
function getMouse(e){
posx = 0;
posy = 0;
if (!e) var e = window.event;
if (e.pageX || e.pageY){
posx = e.pageX;
posy = e.pageY;
}
else if (e.clientX || e.clientY){
posx = e.clientX;
posy = e.clientY;
}
}
function showDiv(id){
var obj = document.getElementById(id);
obj.style.left=posx+'px';
obj.style.top=posy+'px';
obj.style.display='block';
}
...
<body onMouseMove="getMouse(event)">
function showDiv(id){
var obj = document.getElementById(id),
screenWidth = Math.max(document.documentElement.clientWidth, window.innerWidth || 0) - (obj.clientWidth || obj.offsetWidth),
screenHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0) - (obj.clientHeight || obj.offsetHeight);
obj.style.left = Math.min(posx, screenWidth) + 'px';
obj.style.top = Math.min(posy, screenHeight) + 'px';
obj.style.display = 'block';
}
This should keep the div within the view-able area.
You just check to see if the width plus the position etc are more or less than the values of the view-able area.
We also add on the scroll left and scroll top values so our calculations don't mistakenly think it's visible; you can remove that if it's not needed in your case though.
function showDiv(id, posX, posY) {
var obj = document.getElementById(id),
objWidth = obj.offsetWidth,
objHeight = obj.offsetHeight,
docX = window.pageXOffset || document.documentElement.scrollLeft,
docY = window.pageYOffset || document.documentElement.scrollTop,
winWidth = document.documentElement.clientWidth,
winHeight = document.documentElement.clientHeight;
posX += docX;
posY += docY;
if (posX < docX) {
posX = docX;
} else if (posX + objWidth > winWidth + docX) {
posX = docX + (winWidth - objWidth);
}
if (posY < docY) {
posY = docY;
} else if (posY + objHeight > winHeight + docY) {
posY = docY + (winHeight - objHeight);
}
obj.style.top = posY + 'px';
obj.style.left = posX + 'px';
obj.style.display = 'block';
}

Canvas rendering animation slow on safari

Recently I am working on a animation draw on canvas. The animation is to create a radical gradient circle and move the outside circle from one side to the opposite. So it looks like the search light effect.
The Html code is
<canvas id="intro-canvas"></canvas>
The javascript part is:
var canvas = document.getElementById("intro-canvas");
var ctx = canvas.getContext("2d");
canvas.width = $window_w;
canvas.height = $window_h;
var $start = 0;
var $frames = 120;
function intro() {
(function animloop() {
$start++;
if ($start <= $frames) {
$canvas_timer = requestAnimFrame(animloop);
render_canvas();
}
})();
}
var $xmin = (window.innerWidth - 635) / 2;
var $xmax = $xmin + 635;
var $ymin = (window.innerHeight - 300) / 2;
var $ymax = $ymin + 300;
var centerx = randomIntFromInterval($xmin, $xmax);
var centery = randomIntFromInterval($ymin, $ymax);
var center_disx = randomIntFromInterval(450, 500);
var center_disy = randomIntFromInterval(450, 500);
var xstep = (center_disx + center_disx - 280) / 120;
var ystep = (center_disy + center_disy - 280) / 120;
if (centerx > window.innerWidth / 2) {
var centerx2 = centerx + center_disx;
} else {
var centerx2 = centerx - center_disx;
}
if (centery > window.innerHeight / 2) {
var centery2 = centery + center_disy;
} else {
var centery2 = centery - center_disy;
}
var $r1 = randomIntFromInterval(150, 200);
var $r2 = $r1 + randomIntFromInterval(700, 750);
function render_canvas() {
ctx.clearRect(0, 0, $window_w, $window_h);
var grd = ctx.createRadialGradient(centerx, centery, $r1, centerx2, centery2, $r2);
$r2 -= 2;
if (centerx > window.innerWidth / 2) {
centerx2 -= (xstep);
} else {
centerx2 += (xstep);
}
if (centery > window.innerHeight / 2) {
centery2 -= (ystep);
} else {
centery2 += (ystep);
}
grd.addColorStop(0, "#531f69");
grd.addColorStop(0.5, "#8e217d");
grd.addColorStop(1, "#ffffff");
ctx.fillStyle = grd;
ctx.fillRect(0, 0, $window_w, $window_h);
}
function render_last_canvas() {
$('#intro > img').animate({
opacity : 1
}, 2000);
}
function randomIntFromInterval(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
Now the code is working good in chrome but when it comes to safari, the animation becomes very slow. Does anyone have any idea of the performance of canvas radical gradient animation on safari?
Interesting thing is in jsfiddle: http://jsfiddle.net/lhray/qVLps/, it works fine.
Thanks for any ideas.
BTW, my safari is 7.0.4 and OS is OS X 10.9.3
I think this issue is inherent to the Apple WebKit browser engine. Somehow chrome browser engine is able to use WebGL more efficient than Safari.
That's the only thing that explains why the fiddle works fine in safari, but when it comes to safari itself, the rendering process becomes slow.
This article compares Safari canvas performance.
Here's a related question as well:
Canvas drawing takes a lot of time on Safari but not on Chrome or FF

hammer drag center

I'm using Hammers library for my app. How can I drag my element from any point that I touch and not by center ? thanks !
var hammertime = Hammer(document.getElementById('contentTab'), {
transform_always_block:true,
transform_min_scale: 1,
drag_block_horizontal: true,
drag_block_vertical: true,
drag_min_distance: 0,
drag_max_touches: 2,
release: false
});
var rect = document.getElementById('tabella');
var posX=0, posY=0,
scale=1, last_scale,
rotation= 1, last_rotation,
dt =0;
hammertime.on('touch doubletap drag transform', function(ev) {
switch(ev.type) {
case 'doubletap':
if (dt == 0){
dt=1;
scale = 2;
}else if (dt ==1){
dt = 0;
scale = 1;
posX=0;
posY=0;
}
last_rotation = rotation;
break;
case 'touch':
last_scale = scale;
last_rotation = rotation;
break;
case 'drag':
posX = ev.gesture.deltaX;
posY = ev.gesture.deltaY;
break;
case 'transform':
// rotation = last_rotation + ev.gesture.rotation;
scale = Math.max(1, Math.min(last_scale * ev.gesture.scale, 10));
break;
}
// transform!
var transform =
"translate3d("+posX+"px,"+posY+"px, 0) " +
"scale3d("+scale+","+scale+", 0) " ;
"rotate("+rotation+"deg) ";
rect.style.transform = transform;
rect.style.oTransform = transform;
rect.style.msTransform = transform;
rect.style.mozTransform = transform;
rect.style.webkitTransform = transform;
});
Changing
posX = ev.gesture.deltaX;
posY = ev.gesture.deltaY;
to
posX += ev.gesture.deltaX;
posY += ev.gesture.deltaY;
might do the trick.

Categories

Resources