2012-07-16 14 views
12

Mi objetivo es crear un complemento que permita hacer zoom & operaciones de barrido en un área de página, al igual que Google Maps actualmente funciona (es decir: desplazarse con el mouse = acercar/alejar el área, hacer clic & mantener & mover & lanzamiento = paneo).Zoom CSS3 en el cursor del mouse

Al desplazarme, deseo tener una operación de acercamiento centrada en el cursor del mouse.

Para esto, uso sobre la marcha transformaciones de matriz CSS3. La única restricción, aunque obligatoria, es que no puedo usar nada más que las transformaciones de escala de traducción de CSS3 &, con un origen de transformación de 0px 0px.

Panorámica está fuera del alcance de mi pregunta, ya que ya lo tengo funcionando. Cuando se trata de hacer zoom, me cuesta descubrir cuál es el problema en mi código de JavaScript.

El problema debe estar en algún lugar de la función MouseZoom.prototype.zoom, en el cálculo de la traducción en el eje xy el eje y.

primer lugar, aquí es mi código HTML:

<!DOCTYPE html> 
<html> 
<head> 
    <meta name="viewport" content="width = device-width, initial-scale = 1.0, user-scalable = no" /> 
    <meta name="apple-mobile-web-app-capable" content="yes"> 
    <meta name="apple-mobile-web-app-status-bar-style" content="black" /> 
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> 
    <script src="jquery.mousewheel.min.js"></script> 
    <script src="StackOverflow.js"></script> 
    <style type="text/css" media="all"> 
     #drawing { 
      position: absolute; 
      top: 0px; 
      left: 0px; 
      right:0; 
      bottom:0; 
      z-index: 0; 
      background: url(http://catmacros.files.wordpress.com/2009/09/cats_banzai.jpg) no-repeat; 
      background-position: 50% 50%; 
     } 
    </style> 
    <title>Test</title> 
</head> 
<body> 
    <div id="drawing"></div> 
    <script> 
     var renderer = new ZoomPanRenderer("drawing"); 
    </script> 
</body> 
</html> 

Como se puede ver, estoy usando jQuery y el plugin rueda de jQuery ratón de Brandon Aaron, que se puede encontrar aquí: https://github.com/brandonaaron/jquery-mousewheel/

aquí está el contenido del archivo StackOverflow.js:

/***************************************************** 
* Transformations 
****************************************************/ 
function Transformations(translateX, translateY, scale){ 
    this.translateX = translateX; 
    this.translateY = translateY; 
    this.scale = scale; 
} 

/* Getters */ 
Transformations.prototype.getScale = function(){ return this.scale; } 
Transformations.prototype.getTranslateX = function(){ return this.translateX; } 
Transformations.prototype.getTranslateY = function(){ return this.translateY; } 

/***************************************************** 
* Zoom Pan Renderer 
****************************************************/ 
function ZoomPanRenderer(elementId){ 
    this.zooming = undefined; 
    this.elementId = elementId; 
    this.current = new Transformations(0, 0, 1); 
    this.last = new Transformations(0, 0, 1); 
    new ZoomPanEventHandlers(this); 
} 

/* setters */ 
ZoomPanRenderer.prototype.setCurrentTransformations = function(t){ this.current = t; } 
ZoomPanRenderer.prototype.setZooming = function(z){ this.zooming = z; } 

/* getters */ 
ZoomPanRenderer.prototype.getCurrentTransformations = function(){ return this.current; } 
ZoomPanRenderer.prototype.getZooming = function(){ return this.zooming; } 
ZoomPanRenderer.prototype.getLastTransformations = function(){ return this.last; } 
ZoomPanRenderer.prototype.getElementId = function(){ return this.elementId; } 

/* Rendering */ 
ZoomPanRenderer.prototype.getTransform3d = function(t){ 
    var transform3d = "matrix3d("; 
    transform3d+= t.getScale().toFixed(10) + ",0,0,0,"; 
    transform3d+= "0," + t.getScale().toFixed(10) + ",0,0,"; 
    transform3d+= "0,0,1,0,"; 
    transform3d+= t.getTranslateX().toFixed(10) + "," + t.getTranslateY().toFixed(10) + ",0,1)"; 
    return transform3d; 
} 

ZoomPanRenderer.prototype.getTransform2d = function(t){ 
    var transform3d = "matrix("; 
    transform3d+= t.getScale().toFixed(10) + ",0,0," + t.getScale().toFixed(10) + "," + t.getTranslateX().toFixed(10) + "," + t.getTranslateY().toFixed(10) + ")"; 
    return transform3d; 
} 

ZoomPanRenderer.prototype.applyTransformations = function(t){ 
    var elem = $("#" + this.getElementId()); 
    elem.css("transform-origin", "0px 0px"); 
    elem.css("-ms-transform-origin", "0px 0px"); 
    elem.css("-o-transform-origin", "0px 0px"); 
    elem.css("-moz-transform-origin", "0px 0px"); 
    elem.css("-webkit-transform-origin", "0px 0px"); 
    var transform2d = this.getTransform2d(t); 
    elem.css("transform", transform2d); 
    elem.css("-ms-transform", transform2d); 
    elem.css("-o-transform", transform2d); 
    elem.css("-moz-transform", transform2d); 
    elem.css("-webkit-transform", this.getTransform3d(t)); 
} 

/***************************************************** 
* Event handler 
****************************************************/ 
function ZoomPanEventHandlers(renderer){ 
    this.renderer = renderer; 

    /* Disable scroll overflow - safari */ 
    document.addEventListener('touchmove', function(e) { e.preventDefault(); }, false); 

    /* Disable default drag opeartions on the element (FF makes it ready for save)*/ 
    $("#" + renderer.getElementId()).bind('dragstart', function(e) { e.preventDefault(); }); 

    /* Add mouse wheel handler */ 
    $("#" + renderer.getElementId()).bind("mousewheel", function(event, delta) { 
     if(renderer.getZooming()==undefined){ 
      var offsetLeft = $("#" + renderer.getElementId()).offset().left; 
      var offsetTop = $("#" + renderer.getElementId()).offset().top; 
      var zooming = new MouseZoom(renderer.getCurrentTransformations(), event.pageX, event.pageY, offsetLeft, offsetTop, delta); 
      renderer.setZooming(zooming); 

      var newTransformation = zooming.zoom(); 
      renderer.applyTransformations(newTransformation); 
      renderer.setCurrentTransformations(newTransformation); 
      renderer.setZooming(undefined); 
     } 
     return false; 
    }); 
} 

/***************************************************** 
* Mouse zoom 
****************************************************/ 
function MouseZoom(t, mouseX, mouseY, offsetLeft, offsetTop, delta){ 
    this.current = t; 
    this.offsetLeft = offsetLeft; 
    this.offsetTop = offsetTop; 
    this.mouseX = mouseX; 
    this.mouseY = mouseY; 
    this.delta = delta; 
} 

MouseZoom.prototype.zoom = function(){ 
    var previousScale = this.current.getScale(); 
    var newScale = previousScale + this.delta/5; 
    if(newScale<1){ 
     newScale = 1; 
    } 
    var ratio = newScale/previousScale; 

    var imageX = this.mouseX - this.offsetLeft; 
    var imageY = this.mouseY - this.offsetTop; 

    var previousTx = - this.current.getTranslateX() * previousScale; 
    var previousTy = - this.current.getTranslateY() * previousScale; 
    var previousDx = imageX * previousScale; 
    var previousDy = imageY * previousScale; 

    var newTx = (previousTx * ratio + previousDx * (ratio - 1))/newScale; 
    var newTy = (previousTy * ratio + previousDy * (ratio - 1))/newScale; 

    return new Transformations(-newTx, -newTy, newScale); 
} 
+2

Sugerencias: (1) use jsfiddle, es fácil ver el resultado (2) describa "el fallo" en más detalles. –

+0

Solo un consejo: ¿Has visto el complemento llamado zoomooz.js, si no, podría tener muchos marcadores para lo que quieres hacer? Http://janne.aukia.com/zoomooz/ –

Respuesta

28

Usando transform para obtener una biena Le mapas de zoom comportamiento en un elemento de div que pareció una idea interesante, por lo que pagado con un poco =)

me gustaría utilizar transform-origin (y atributos de su hermana por la compatibilidad del navegador) para ajustar el zoom al ratón ubicación en el div que estás escalando. Creo que esto podría hacer lo que quieras. I poner algunos ejemplos en el violín para la ilustración:

Ajuste de la transform-origin

lo tanto, en la función applyTransformations de los suyos que podría ajustar el transform-origin dinámicamente desde el imageX y imageY, si pasamos estos valores de la función MouseZoom (detector de ratón).

var orig = t.getTranslateX().toFixed() + "px " + t.getTranslateY().toFixed() + "px"; 
    elem.css("transform-origin", orig); 
    elem.css("-ms-transform-origin", orig); 
    elem.css("-o-transform-origin", orig); 
    elem.css("-moz-transform-origin", orig); 
    elem.css("-webkit-transform-origin", orig); 

(En este first fiddle example acabo de utilizar su translateX y translateY en Transformations para pasar la ubicación del ratón en el elemento div - en el segundo ejemplo que le cambió el nombre a originX y originY de diferenciar de las variables de traducción.)

el cálculo de la transformada de origen

en su ubicación MouseZoom podemos calcular origen simplemente con imageX/previousScale.

MouseZoom.prototype.zoom = function(){ 
     var previousScale = this.current.getScale(); 
     var newScale = previousScale + this.delta/10; 
     if(newScale<1){ 
      newScale = 1; 
     } 
     var ratio = newScale/previousScale; 

     var imageX = this.mouseX - this.offsetLeft; 
     var imageY = this.mouseY - this.offsetTop; 

     var newTx = imageX/previousScale; 
     var newTy = imageY/previousScale; 

     return new Transformations(newTx, newTy, newScale); 
    } 

De modo que esto funcionará perfectamente si aleja completamente el zoom antes de acercarse a otra posición. Pero para poder cambiar el origen del zoom en cualquier nivel de zoom, podemos combinar el origen y la funcionalidad de traducción.

cambiar el marco de zoom (que se extiende mi respuesta original)

El origen de transformación en la imagen fija se calcula de la misma manera pero usar un translateX y translateY separada para cambiar el marco de zoom (aquí presentado dos nuevas variables que nos ayudan a hacer el truco, así que ahora tenemos originX, originY, translateX y translateY).

MouseZoom.prototype.zoom = function(){ 
     // current scale 
     var previousScale = this.current.getScale(); 
     // new scale 
     var newScale = previousScale + this.delta/10; 
     // scale limits 
     var maxscale = 20; 
     if(newScale<1){ 
      newScale = 1; 
     } 
     else if(newScale>maxscale){ 
      newScale = maxscale; 
     } 
     // current cursor position on image 
     var imageX = (this.mouseX - this.offsetLeft).toFixed(2); 
     var imageY = (this.mouseY - this.offsetTop).toFixed(2); 
     // previous cursor position on image 
     var prevOrigX = (this.current.getOriginX()*previousScale).toFixed(2); 
     var prevOrigY = (this.current.getOriginY()*previousScale).toFixed(2); 
     // previous zooming frame translate 
     var translateX = this.current.getTranslateX(); 
     var translateY = this.current.getTranslateY(); 
     // set origin to current cursor position 
     var newOrigX = imageX/previousScale; 
     var newOrigY = imageY/previousScale; 
     // move zooming frame to current cursor position 
     if ((Math.abs(imageX-prevOrigX)>1 || Math.abs(imageY-prevOrigY)>1) && previousScale < maxscale) { 
      translateX = translateX + (imageX-prevOrigX)*(1-1/previousScale); 
      translateY = translateY + (imageY-prevOrigY)*(1-1/previousScale); 
     } 
     // stabilize position by zooming on previous cursor position 
     else if(previousScale != 1 || imageX != prevOrigX && imageY != prevOrigY) { 
      newOrigX = prevOrigX/previousScale; 
      newOrigY = prevOrigY/previousScale; 
     } 
     return new Transformations(newOrigX, newOrigY, translateX, translateY, newScale); 
    } 

Para este ejemplo ajustar la secuencia de comandos original, un poco más y añade el second fiddle example.

Ahora acercamos y alejamos el cursor del mouse desde cualquier nivel de acercamiento. Pero debido al cambio de marco, terminamos moviendo el div original ("midiendo la tierra") ... lo cual se ve raro si trabajas con un objeto de ancho y alto limitado (acercas un extremo, alejas el zoom en otro extremo, y avanzamos como un inchworm).

evitar el efecto "gusano"

Para evitar esto se podría, por ejemplo, añadir limitaciones para que el borde de la imagen izquierda no puede moverse a la derecha de sus x originales de coordenadas, el borde de la imagen de arriba no se puede mover menor que su posición y original, y así sucesivamente para las otras dos fronteras. Pero luego el zoom/out no estará completamente vinculado al cursor, sino también al borde de la imagen (notará que la imagen se desliza en su lugar) en example 3.

if(this.delta <= 0){ 
     var width = 500; // image width 
     var height = 350; // image height 
     if(translateX+newOrigX+(width - newOrigX)*newScale <= width){ 
      translateX = 0; 
      newOrigX = width; 
     } 
     else if (translateX+newOrigX*(1-newScale) >= 0){ 
      translateX = 0; 
      newOrigX = 0;   
     } 
     if(translateY+newOrigY+(height - newOrigY)*newScale <= height){ 
      translateY = 0; 
      newOrigY = height; 
     } 
     else if (translateY+newOrigY*(1-newScale) >= 0){ 
      translateY = 0; 
      newOrigY = 0; 
     } 
    } 

Otro (un poco cutre) opción sería la de restablecer simplemente el marco de traducir cuando se aleja por completo (== escala 1).

Sin embargo, no tendría este problema si va a tratar con elementos continuos (borde izquierdo y derecho y borde superior e inferior unidos) o simplemente con elementos extremadamente grandes.

Para terminar todo con un toque agradable, podemos agregar un marco principal con un desbordamiento oculto alrededor de nuestro objeto de escala. Entonces, el área de la imagen no cambia con el zoom. Ver jsfiddle example 4.

0

Hicimos una biblioteca para este reaccionan: https://www.npmjs.com/package/react-map-interaction

Maneja zoom y panorámica y funciona en móviles y de escritorio.

La fuente es bastante corto y fácil de leer, pero para responder a su pregunta aquí más directamente, utilizamos este CSS transformar:

const transform = `translate(${translation.x}px, ${translation.y}px) scale(${scale})`; 
const style = { 
    transform: transform, 
    transformOrigin: '0 0 ' 
}; 

// render the div with that style 

Uno de los trucos principal es calcular adecuadamente el diff entre el puntero/ratón inicial estado hacia abajo y el estado actual cuando se produce un movimiento de toque/mouse. Cuando el mouse baja, captura las coordenadas. Luego, en cada movimiento del mouse (hasta que el mouse suba), calcule la diferencia en la distancia. Esa diferencia es lo que necesita para compensar la traducción para asegurarse de que el punto inicial debajo del cursor sea el punto focal del zoom.

Cuestiones relacionadas