2012-07-16 60 views
12

我的目标是创建一个插件,可以让页面上的区域缩放&平移操作,随便怎么样谷歌地图目前工程(意思是:用鼠标滚动= /缩小区域的缩放,点击& hold & move & release = panning)。CSS3的鼠标光标缩放

滚动时,我希望有中心上的鼠标光标的变焦操作。

为此,我使用即时CSS3矩阵转换。唯一但是强制性的限制是我不能使用比CSS3 translate & scale转换更多的东西,转换原点为0px 0px。

平移是我的问题的范围,因为我有它已经工作。 当谈到缩放时,我正在努力弄清楚我的javascript代码中的毛刺在哪里。

问题必须某处MouseZoom.prototype.zoom功能,在x轴和y轴平移的计算。

首先,这里是我的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> 

正如你所看到的,我使用jQuery和从布兰登·艾伦jQuery的鼠标滚轮插件,它可以在这里找到: https://github.com/brandonaaron/jquery-mousewheel/

这里是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

建议:(1)使用jsfiddle,很容易看到结果(2)更详细地描述“毛刺”。 – 2013-01-04 05:20:08

+0

只是一个提示 - 你有没有看到名为zoomooz.js的插件,如果不是,它可能会有很多你想要制作的指针 - http://janne.aukia.com/zoomooz/ – 2013-01-31 10:14:47

回答

28

使用transform得到goog乐图的div元素似乎是一个有趣的想法上缩放行为,所以我用了一点祈祷,=)

我会用transform-origin(和浏览器兼容性的姐妹属性)和变焦调节鼠标您正在缩放的​​div上的位置。我认为这可以做你想做的事。 我把一些实例上小提琴来加以说明:

调整transform-origin

所以在你的applyTransformations功能,我们可以动态地从imageXimageY调整transform-origin,如果我们从MouseZoom(鼠标监听)功能通过这个值。

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); 

(在此first fiddle example我只是用你的translateXTransformationstranslateY传递div元素上鼠标的位置 - 在我把它改名为originXoriginY从翻译变量来区分第二个例子。 )

计算转换原点

在你MouseZoom我们可以计算出原点位置只需用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); 
    } 

因此,如果您在放大其他位置之前完全缩小,则此功能将完美工作。但为了能够在任何缩放级别更改缩放原点,我们可以结合原点和翻译功能。

转移变焦框架(延长我原来的答复)

的图像变换起源至今仍是计算方式相同,但我们使用一个单独的平移X和translateY的变焦移(在这里我引入了两个新的变量,帮助我们做到这一点 - 所以现在我们有originXoriginYtranslateXtranslateY)。

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); 
    } 

在这个例子中我调整了原来的剧本多一点,并且增加了second fiddle example

现在我们从任何缩放级别放大和缩小鼠标光标。但由于帧的移动,我们最终会移动原来的div(“测量地球”)......如果您使用宽度和高度有限的物体进行处理,这看起来很有趣(一端放大,一端放大另一端,我们像一只尺w一样向前移动)。

避免了“尺蠖效应”

为了避免这种情况,你可以例如添加限制,使得左图像边界不能移动到其原始x坐标的右侧,顶部图像边界不能移动低于其原来的y位置,其他两个边界等等。但是,然后在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; 
     } 
    } 

另一个(有点蹩脚的)选择是简单的复位,当你缩小完全(规模== 1)框架翻译。

但是,如果您要处理连续元素(左右边界和上下边界绑定在一起)或者只是处理非常大的元素,则不会出现此问题。

为了完美地完成一切 - 我们可以在缩放对象周围添加一个隐藏溢出的父框架。所以图像区域不会随着缩放而改变。请参阅jsfiddle example 4

0

我们做了一个反应库这样的: https://www.npmjs.com/package/react-map-interaction

它处理缩放和平移,并适用于移动和桌面。

来源是相当短和可读性,但这里更直接地回答你的问题,我们使用这个CSS变换:

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

// render the div with that style 

一个主要招数是正确计算初始指针/鼠标之间的差异当发生触摸/鼠标移动时,该状态为当前状态。当鼠标发生故障时,捕捉坐标。然后在每次鼠标移动时(直到鼠标上移)计算距离中的差异。该差异是您需要抵消平移以确保光标下的初始点是缩放的焦点。