CSS3 zoom sobre el cursor del mouse

Mi objetivo es crear un complemento que permita las operaciones de zoom y paneo en un área de la página, tal como funciona actualmente Google Maps (lo que significa: desplazarse con el mouse = acercar / alejar el área, hacer clic y mantener y mover y liberar = panning) ).

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

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

La panorámica está fuera del alcance de mi pregunta, ya que ya la tengo funcionando. Cuando se trata de hacer zoom, estoy luchando para descubrir dónde está el fallo en mi código 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 x y el eje y.

Primero, aquí está 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 puede ver, estoy usando Jquery y el plugin de la rueda del ratón jquery 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);
}

Respuestas a la pregunta(2)

Su respuesta a la pregunta