javascript - Pellizcar para hacer zoom usando Hammer.js
jquery pinchzoom (4)
Estoy tratando de implementar pellizcar para ampliar con hammer.js
Aquí está mi HTML-
<script src="//cdnjs.cloudflare.com/ajax/libs/hammer.js/1.0.5/hammer.min.js"></script>
<div id="pinchzoom">
<div>
<img id="rect" src="http://blog.gettyimages.com/wp-content/uploads/2013/01/Siberian-Tiger-Running-Through-Snow-Tom-Brakefield-Getty-Images-200353826-001.jpg" width="2835" height="4289" ondragstart="return false" alt="" />
</div>
</div>
Aquí está mi GUIÓN
var hammertime = Hammer(document.getElementById(''pinchzoom''), {
transform_always_block: true,
transform_min_scale: 1,
drag_block_horizontal: false,
drag_block_vertical: false,
drag_min_distance: 0
});
var rect = document.getElementById(''rect'');
var posX=0, posY=0,
scale=1, last_scale,
rotation= 1, last_rotation;
hammertime.on(''touch drag transform'', function(ev) {
switch(ev.type) {
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) ";
rect.style.transform = transform;
rect.style.oTransform = transform;
rect.style.msTransform = transform;
rect.style.mozTransform = transform;
rect.style.webkitTransform = transform;
});
Funciona bien, pero no puedo desplazar la imagen. En el transform3d sin comentario funciona, pero la imagen pierde su posición en el arrastre. No puedo usar jQuery.
Agregue las variables last_posX
y last_posY
para tener en cuenta el cambio de posición cuando haya realizado la traducción. actualícelos en dragend para que la próxima vez que se capture un evento de arrastre tenga en cuenta dónde terminó el último arrastre
var posX=0, posY=0,
scale=1, last_scale,
last_posX=0, last_posY=0,
max_pos_x=0, max_pos_y=0;
hammertime.on(''touch drag transform dragend'', function(ev) {
switch(ev.type) {
case ''touch'':
last_scale = scale;
break;
case ''drag'':
if(scale != 1){
posX = last_posX + ev.gesture.deltaX;
posY = last_posY + ev.gesture.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 ''transform'':
scale = Math.max(1, Math.min(last_scale * ev.gesture.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 ''dragend'':
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;
});
Echa un vistazo a la demostración Pinch Zoom and Pan with HammerJS . Este ejemplo ha sido probado en Android, iOS y Windows Phone.
Puede encontrar el código fuente en Pinch Zoom y Pan con HammerJS .
Para su comodidad, aquí está el código fuente:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport"
content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1">
<title>Pinch Zoom</title>
</head>
<body>
<div>
<div style="height:150px;background-color:#eeeeee">
Ignore this area. Space is needed to test on the iPhone simulator as pinch simulation on the
iPhone simulator requires the target to be near the middle of the screen and we only respect
touch events in the image area. This space is not needed in production.
</div>
<style>
.pinch-zoom-container {
overflow: hidden;
height: 300px;
}
.pinch-zoom-image {
width: 100%;
}
</style>
<script src="https://hammerjs.github.io/dist/hammer.js"></script>
<script>
var MIN_SCALE = 1; // 1=scaling when first loaded
var MAX_SCALE = 64;
// HammerJS fires "pinch" and "pan" events that are cumulative in nature and not
// deltas. Therefore, we need to store the "last" values of scale, x and y so that we can
// adjust the UI accordingly. It isn''t until the "pinchend" and "panend" events are received
// that we can set the "last" values.
// Our "raw" coordinates are not scaled. This allows us to only have to modify our stored
// coordinates when the UI is updated. It also simplifies our calculations as these
// coordinates are without respect to the current scale.
var imgWidth = null;
var imgHeight = null;
var viewportWidth = null;
var viewportHeight = null;
var scale = null;
var lastScale = null;
var container = null;
var img = null;
var x = 0;
var lastX = 0;
var y = 0;
var lastY = 0;
var pinchCenter = null;
// We need to disable the following event handlers so that the browser doesn''t try to
// automatically handle our image drag gestures.
var disableImgEventHandlers = function () {
var events = [''onclick'', ''onmousedown'', ''onmousemove'', ''onmouseout'', ''onmouseover'',
''onmouseup'', ''ondblclick'', ''onfocus'', ''onblur''];
events.forEach(function (event) {
img[event] = function () {
return false;
};
});
};
// Traverse the DOM to calculate the absolute position of an element
var absolutePosition = function (el) {
var x = 0,
y = 0;
while (el !== null) {
x += el.offsetLeft;
y += el.offsetTop;
el = el.offsetParent;
}
return { x: x, y: y };
};
var restrictScale = function (scale) {
if (scale < MIN_SCALE) {
scale = MIN_SCALE;
} else if (scale > MAX_SCALE) {
scale = MAX_SCALE;
}
return scale;
};
var restrictRawPos = function (pos, viewportDim, imgDim) {
if (pos < viewportDim/scale - imgDim) { // too far left/up?
pos = viewportDim/scale - imgDim;
} else if (pos > 0) { // too far right/down?
pos = 0;
}
return pos;
};
var updateLastPos = function (deltaX, deltaY) {
lastX = x;
lastY = y;
};
var translate = function (deltaX, deltaY) {
// We restrict to the min of the viewport width/height or current width/height as the
// current width/height may be smaller than the viewport width/height
var newX = restrictRawPos(lastX + deltaX/scale,
Math.min(viewportWidth, curWidth), imgWidth);
x = newX;
img.style.marginLeft = Math.ceil(newX*scale) + ''px'';
var newY = restrictRawPos(lastY + deltaY/scale,
Math.min(viewportHeight, curHeight), imgHeight);
y = newY;
img.style.marginTop = Math.ceil(newY*scale) + ''px'';
};
var zoom = function (scaleBy) {
scale = restrictScale(lastScale*scaleBy);
curWidth = imgWidth*scale;
curHeight = imgHeight*scale;
img.style.width = Math.ceil(curWidth) + ''px'';
img.style.height = Math.ceil(curHeight) + ''px'';
// Adjust margins to make sure that we aren''t out of bounds
translate(0, 0);
};
var rawCenter = function (e) {
var pos = absolutePosition(container);
// We need to account for the scroll position
var scrollLeft = window.pageXOffset ? window.pageXOffset : document.body.scrollLeft;
var scrollTop = window.pageYOffset ? window.pageYOffset : document.body.scrollTop;
var zoomX = -x + (e.center.x - pos.x + scrollLeft)/scale;
var zoomY = -y + (e.center.y - pos.y + scrollTop)/scale;
return { x: zoomX, y: zoomY };
};
var updateLastScale = function () {
lastScale = scale;
};
var zoomAround = function (scaleBy, rawZoomX, rawZoomY, doNotUpdateLast) {
// Zoom
zoom(scaleBy);
// New raw center of viewport
var rawCenterX = -x + Math.min(viewportWidth, curWidth)/2/scale;
var rawCenterY = -y + Math.min(viewportHeight, curHeight)/2/scale;
// Delta
var deltaX = (rawCenterX - rawZoomX)*scale;
var deltaY = (rawCenterY - rawZoomY)*scale;
// Translate back to zoom center
translate(deltaX, deltaY);
if (!doNotUpdateLast) {
updateLastScale();
updateLastPos();
}
};
var zoomCenter = function (scaleBy) {
// Center of viewport
var zoomX = -x + Math.min(viewportWidth, curWidth)/2/scale;
var zoomY = -y + Math.min(viewportHeight, curHeight)/2/scale;
zoomAround(scaleBy, zoomX, zoomY);
};
var zoomIn = function () {
zoomCenter(2);
};
var zoomOut = function () {
zoomCenter(1/2);
};
var onLoad = function () {
img = document.getElementById(''pinch-zoom-image-id'');
container = img.parentElement;
disableImgEventHandlers();
imgWidth = img.width;
imgHeight = img.height;
viewportWidth = img.offsetWidth;
scale = viewportWidth/imgWidth;
lastScale = scale;
viewportHeight = img.parentElement.offsetHeight;
curWidth = imgWidth*scale;
curHeight = imgHeight*scale;
var hammer = new Hammer(container, {
domEvents: true
});
hammer.get(''pinch'').set({
enable: true
});
hammer.on(''pan'', function (e) {
translate(e.deltaX, e.deltaY);
});
hammer.on(''panend'', function (e) {
updateLastPos();
});
hammer.on(''pinch'', function (e) {
// We only calculate the pinch center on the first pinch event as we want the center to
// stay consistent during the entire pinch
if (pinchCenter === null) {
pinchCenter = rawCenter(e);
var offsetX = pinchCenter.x*scale - (-x*scale + Math.min(viewportWidth, curWidth)/2);
var offsetY = pinchCenter.y*scale - (-y*scale + Math.min(viewportHeight, curHeight)/2);
pinchCenterOffset = { x: offsetX, y: offsetY };
}
// When the user pinch zooms, she/he expects the pinch center to remain in the same
// relative location of the screen. To achieve this, the raw zoom center is calculated by
// first storing the pinch center and the scaled offset to the current center of the
// image. The new scale is then used to calculate the zoom center. This has the effect of
// actually translating the zoom center on each pinch zoom event.
var newScale = restrictScale(scale*e.scale);
var zoomX = pinchCenter.x*newScale - pinchCenterOffset.x;
var zoomY = pinchCenter.y*newScale - pinchCenterOffset.y;
var zoomCenter = { x: zoomX/newScale, y: zoomY/newScale };
zoomAround(e.scale, zoomCenter.x, zoomCenter.y, true);
});
hammer.on(''pinchend'', function (e) {
updateLastScale();
updateLastPos();
pinchCenter = null;
});
hammer.on(''doubletap'', function (e) {
var c = rawCenter(e);
zoomAround(2, c.x, c.y);
});
};
</script>
<button onclick="zoomIn()">Zoom In</button>
<button onclick="zoomOut()">Zoom Out</button>
<div class="pinch-zoom-container">
<img id="pinch-zoom-image-id" class="pinch-zoom-image" onload="onLoad()"
src="https://hammerjs.github.io/assets/img/pano-1.jpg">
</div>
</div>
</body>
</html>
(Por cierto, hay mucha información buena arriba, pero no pude obtener ninguno de los ejemplos para trabajar realmente y hay algunos detalles sutiles que quería implementar, como la consideración del centro de acercamiento).
No estaba realmente satisfecho con las soluciones, así que también creé una.
Puede jugar con él en https://bl.ocks.org/stephanbogner/06c3e0d3a1c8fcca61b5345e1af80798
El código es más complejo, pero se comporta como en iOS (probado en iPad y iPhone):
- Toca dos veces la imagen y se enfoca en esa ubicación exacta
- Mientras pellizca el centro del zoom se encuentra entre los dedos y puede arrastrar mientras pellizca
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>HammerJS Pinch Zoom and Drag</title>
<style type="text/css">
body{
margin: 0;
padding: 0;
}
#dragWrapper{
margin: 40px;
background: whitesmoke;
width: calc(100vw - 80px);
height: calc(100vh - 80px);
position: relative;
}
#drag {
touch-action: auto;
height: 300px;
width: 200px;
position: absolute;
left: 0;
top: 0;
background-image: url(''https://upload.wikimedia.org/wikipedia/commons/thumb/4/4a/Bergelut_dengan_asap_nan_beracun.jpg/318px-Bergelut_dengan_asap_nan_beracun.jpg'');
background-size: cover;
}
</style>
</head>
<body>
<div id="dragWrapper">
<div id="drag"></div>
</div>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/hammer.js/2.0.8/hammer.min.js"></script>
<script type="text/javascript">
var element = document.getElementById(''drag'')
var hammertime = new Hammer(element, {});
hammertime.get(''pinch'').set({ enable: true });
hammertime.get(''pan'').set({ threshold: 0 });
var fixHammerjsDeltaIssue = undefined;
var pinchStart = { x: undefined, y: undefined }
var lastEvent = undefined;
var originalSize = {
width: 200,
height: 300
}
var current = {
x: 0,
y: 0,
z: 1,
zooming: false,
width: originalSize.width * 1,
height: originalSize.height * 1,
}
var last = {
x: current.x,
y: current.y,
z: current.z
}
function getRelativePosition(element, point, originalSize, scale) {
var domCoords = getCoords(element);
var elementX = point.x - domCoords.x;
var elementY = point.y - domCoords.y;
var relativeX = elementX / (originalSize.width * scale / 2) - 1;
var relativeY = elementY / (originalSize.height * scale / 2) - 1;
return { x: relativeX, y: relativeY }
}
function getCoords(elem) { // crossbrowser version
var box = elem.getBoundingClientRect();
var body = document.body;
var docEl = document.documentElement;
var scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop;
var scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft;
var clientTop = docEl.clientTop || body.clientTop || 0;
var clientLeft = docEl.clientLeft || body.clientLeft || 0;
var top = box.top + scrollTop - clientTop;
var left = box.left + scrollLeft - clientLeft;
return { x: Math.round(left), y: Math.round(top) };
}
function scaleFrom(zoomOrigin, currentScale, newScale) {
var currentShift = getCoordinateShiftDueToScale(originalSize, currentScale);
var newShift = getCoordinateShiftDueToScale(originalSize, newScale)
var zoomDistance = newScale - currentScale
var shift = {
x: currentShift.x - newShift.x,
y: currentShift.y - newShift.y,
}
var output = {
x: zoomOrigin.x * shift.x,
y: zoomOrigin.y * shift.y,
z: zoomDistance
}
return output
}
function getCoordinateShiftDueToScale(size, scale){
var newWidth = scale * size.width;
var newHeight = scale * size.height;
var dx = (newWidth - size.width) / 2
var dy = (newHeight - size.height) / 2
return {
x: dx,
y: dy
}
}
hammertime.on(''doubletap'', function(e) {
var scaleFactor = 1;
if (current.zooming === false) {
current.zooming = true;
} else {
current.zooming = false;
scaleFactor = -scaleFactor;
}
element.style.transition = "0.3s";
setTimeout(function() {
element.style.transition = "none";
}, 300)
var zoomOrigin = getRelativePosition(element, { x: e.center.x, y: e.center.y }, originalSize, current.z);
var d = scaleFrom(zoomOrigin, current.z, current.z + scaleFactor)
current.x += d.x;
current.y += d.y;
current.z += d.z;
last.x = current.x;
last.y = current.y;
last.z = current.z;
update();
})
hammertime.on(''pan'', function(e) {
if (lastEvent !== ''pan'') {
fixHammerjsDeltaIssue = {
x: e.deltaX,
y: e.deltaY
}
}
current.x = last.x + e.deltaX - fixHammerjsDeltaIssue.x;
current.y = last.y + e.deltaY - fixHammerjsDeltaIssue.y;
lastEvent = ''pan'';
update();
})
hammertime.on(''pinch'', function(e) {
var d = scaleFrom(pinchZoomOrigin, last.z, last.z * e.scale)
current.x = d.x + last.x + e.deltaX;
current.y = d.y + last.y + e.deltaY;
current.z = d.z + last.z;
lastEvent = ''pinch'';
update();
})
var pinchZoomOrigin = undefined;
hammertime.on(''pinchstart'', function(e) {
pinchStart.x = e.center.x;
pinchStart.y = e.center.y;
pinchZoomOrigin = getRelativePosition(element, { x: pinchStart.x, y: pinchStart.y }, originalSize, current.z);
lastEvent = ''pinchstart'';
})
hammertime.on(''panend'', function(e) {
last.x = current.x;
last.y = current.y;
lastEvent = ''panend'';
})
hammertime.on(''pinchend'', function(e) {
last.x = current.x;
last.y = current.y;
last.z = current.z;
lastEvent = ''pinchend'';
})
function update() {
current.height = originalSize.height * current.z;
current.width = originalSize.width * current.z;
element.style.transform = "translate3d(" + current.x + "px, " + current.y + "px, 0) scale(" + current.z + ")";
}
</script>
</body>
</html>
Para hammer.js 2.0+, he respondido a @DGS y lo he cambiado para adaptarme a lo que estaba haciendo con cordova y con intel-xdk así que es JS puro y hammer.js 2.0 para webkit .
Mi implementación le permite hacer zoom y arrastrar al mismo tiempo, no independientemente el uno del otro como se indicó anteriormente, y proporciona una sensación más nativa. También implementa la doble pulsación para acercar ( y alejar ). Lo tengo configurado para hacer zoom entre .999
y 4
, pero puedes hacer lo que quieras simplemente cambiando esos valores. Por lo tanto, si solo copia y pega esto, probablemente hará lo que usted espera ( en el webkit ).
¡Gracias a Eight Media y @DGS por ayudarme a empezar! Siéntete libre de mejorarlo TAN.
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;
}
});
}
Para implementar solo llámalo con hammerIt (document.getElementById ("imagid")); después de que el elemento se ha cargado. Puede invocar esto en tantos elementos como desee.
¡Aclamaciones!