CSS3缩放鼠标光标 [英] CSS3 zooming on mouse cursor

查看:187
本文介绍了CSS3缩放鼠标光标的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的目标是创建一个插件,使缩放&在页面区域上进行平移操作,就像Google地图目前的工作方式(意思是:使用鼠标滚动:放大/缩小区域,点击并按住& amp; move& release =平移)。



滚动时,我希望有一个以鼠标光标为中心的缩放操作。



为此,我使用了CSS3矩阵变换。唯一但是强制性的约束是,我不能使用任何东西比CSS3 translate&缩放转换,转换起点为0px 0px。



平移超出了我的问题范围,因为我已经工作了。
当涉及到缩放时,我很难找出在我的JavaScript代码中的毛刺在哪里。



问题必须在MouseZoom.prototype



首先,这里是我的HTML代码:

 <!DOCTYPE html> 
< html>
< head>
< meta name =viewportcontent =width = device-width,initial-scale = 1.0,user-scalable = no/>
< meta name =apple-mobile-web-app-capablecontent =yes>
< meta name =apple-mobile-web-app-status-bar-stylecontent =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 / cssmedia =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>测试< / title>
< / head>
< body>
< div id =drawing>< / div>
< script>
var renderer = new ZoomPanRenderer(drawing);
< / script>
< / body>
< / html>

如你所见,我使用Jquery和Brandon Aaron的jQuery鼠标滚轮插件可以在这里找到:
https://github.com/brandonaaron/jquery-mousewheel/



以下是StackOverflow.js文件的内容:

  / *********************************************** ****** 
*转换
********************************** ****************** /
function transformations(translateX,translateY,scale){
this.translateX = translateX;
this.translateY = translateY;
this.scale = scale;
}

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

/ **************************************** *************
*缩放Pan呈现器
*********************** ******************************* /
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 $ b transform3d + =0,0,1,0,;
transform3d + = t.getTranslateX()。toFixed(10)+,+ t.getTranslateY()。toFixed(10)+ ,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 )+);
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));
}

/ ************************************ *****************
*事件处理程序
********************** ****************************** /
function ZoomPanEventHandlers(renderer){
this.renderer = renderer ;

/ *禁用滚动溢出 - safari * /
document.addEventListener('touchmove',function(e){e.preventDefault();},false);

/ *在元素上禁用默认的拖动操作(FF使它准备好保存)* /
$(#+ renderer.getElementId())。bind('dragstart' function(e){e.preventDefault();});

/ *添加鼠标滚轮处理程序* /
$(#+ 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;
});
}

/ ************************************ *****************
*鼠标变焦
********************** ********************************** /
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);
}


解决方案

使用 transform div 元素上获取 google地图缩放行为有趣的想法,所以我付了它一点=)



我会使用 transform-origin 浏览器兼容性的属性),以调整缩放到缩放的div上的鼠标位置。我认为这可以做你想要的。
我举一些例子来说明:





调整 transform-origin



所以在你的 applyTransformations 函数中,我们可以调整 transform-origin <如果我们从传递此值,则从 imageX imageY > MouseZoom (鼠标监听器)函数。

  var orig = t.getTranslateX )+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);

(在此第一个小提示示例我只是使用中的 translateX translateY / code>将鼠标的位置传递给div元素 - 在第二个示例中,我将其重命名为 originX originY 以区分翻译变量。)



计算转换原点



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

因此,如果您在放大之前缩小位置。



移动缩放框架(延伸)缩放框架我的原始答案)



图像上的变换原点仍然以相同的方式计算,但我们使用单独的translateX和translateY来移动缩放帧(这里我介绍了两个新变量,帮助我们做的窍门 - 所以现在我们有 originX originY translateX 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;
}
//当前光标在图像上的位置
var imageX =(this.mouseX - this.offsetLeft).toFixed(2);
var imageY =(this.mouseY - this.offsetTop).toFixed(2);
//图像上的上一个光标位置
var prevOrigX =(this.current.getOriginX()* previousScale).toFixed(2);
var prevOrigY =(this.current.getOriginY()* previousScale).toFixed(2);
//以前的缩放框架翻译
var translateX = this.current.getTranslateX();
var translateY = this.current.getTranslateY();
//将起点设置为当前光标位置
var newOrigX = imageX / previousScale;
var newOrigY = imageY / previousScale;
//将缩放框移动到当前光标位置
if((Math.abs(imageX-prevOrigX)> 1 || Math.abs(imageY-prevOrigY)> 1)&& < maxscale){
translateX = translateX +(imageX-prevOrigX)*(1-1 / previousScale);
translateY = translateY +(imageY-prevOrigY)*(1-1 / previousScale);
}
//通过放大上一个光标位置来稳定位置
else if(previousScale!= 1 || imageX!= prevOrigX&& imageY!= prevOrigY){
newOrigX = prevOrigX / previousScale;
newOrigY = prevOrigY / previousScale;
}
return new Transformations(newOrigX,newOrigY,translateX,translateY,newScale);
}

在这个例子中,我调整了原来的脚本, a href =http://jsfiddle.net/mturjak/d2epQ/>第二个小提示示例。



现在我们放大和缩小鼠标光标从任何缩放级别。但是由于帧移位,我们最终会移动原始的div(测量地球)...这看起来很有趣,如果你使用有限的宽度和高度的对象(放大在一端,缩小


$ b $ b

为避免这种情况,您可以添加限制,使左侧图像边框不能移动到其原始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;
}
}

另一个但是,如果你将处理连续的元素(左边和右边),你不会有这个问题。右边缘和顶部和底部边缘绑定在一起)或只是与非常大的元素。



为了完成一切与一个漂亮的触摸 - 我们可以添加一个父框架与隐藏溢出围绕我们的缩放对象。因此,图像区域不随缩放而变化。请参见 jsfiddle示例4


My goal is to create a plugin that enables zooming & panning operations on a page area, just like how Google Maps currently works (meaning: scrolling with the mouse = zooming in/out of the area, click & hold & move & release = panning).

When scrolling, I wish to have a zoom operation centered on the mouse cursor.

For this, I use on-the-fly CSS3 matrix transformations. The only, yet mandatory, constraint is that I cannot use anything else than CSS3 translate & scale transformations, with a transform origin of 0px 0px.

Panning is out of the scope of my question, since I have it working already. When it comes to zooming, I am struggling to figure out where the glitch is in my javascript code.

The problem must be somewhere in the MouseZoom.prototype.zoom function, in the calculation of the translation on the x axis and y axis.

First, here is my HTML code:

<!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>

As you can see, I am using Jquery and the jquery mouse wheel plugin from Brandon Aaron, which can be found here: https://github.com/brandonaaron/jquery-mousewheel/

Here is the content of the StackOverflow.js file:

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

解决方案

Using transform to get a google maps zooming behavior on a div element seemed like an interesting idea, so I payed with it a little =)

I would use transform-origin (and its sister attributes for browser compatibility) to adjust the zooming to the mouse location on the div that you are scaling. I think this could do what you want. I put some examples on fiddle for illustration:

Adjusting the transform-origin

So in the applyTransformations function of yours we could adjust the transform-origin dynamically from the imageX and imageY, if we pass this values from the MouseZoom (mouse listener) function.

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

(In this first fiddle example I just used your translateX and translateY in Transformations to pass the location of the mouse on the div element - in the second example I renamed it to originX and originY to differentiate from the translation variables.)

Calculating the transform origin

In your MouseZoom we can calculate origin location simply with 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);
    }

So this will work perfectly if you zoom out completely before zooming in on a different position. But to be able to change zoom origin at any zoom level, we can combine the origin and translation functionality.

Shifting the zooming frame (extending my original answer)

The transform origin on the image is still calculated the same way but we use a separate translateX and translateY to shift the zooming frame (here I introduced two new variables that help us do the trick - so now we have originX, originY, translateX and 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);
    }

For this example I adjusted the your original script a little more and added the second fiddle example.

Now we zoom in and out on the mouse cursor from any zoom level. But because of the frame shift we end up moving the original div around ("measuring the earth") ... which looks funny if you work with an object of limited width and hight (zoom-in at one end, zoom-out at another end, and we moved forward like an inchworm).

Avoiding the "inchworm" effect

To avoid this you could for example add limitations so that the left image border can not move to the right of its original x coordinate, the top image border can not move lower than its original y position, and so on for the other two borders. But then the zoom/out will not be completely bound to the cursor, but also by the edge of the image (you will notice the image slide into place) in 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;
        }
    }

Another (a bit crappy) option would be to simply reset the frame translate when you zoom out completely (scale==1).

However, you would not have this problem if you will be dealing with continuous elements (left and right edge and top and bottom edge bound together) or just with extremely big elements.

To finish everything off with a nice touch - we can add a parent frame with hidden overflow around our scaling object. So the image area does not change with zooming. See jsfiddle example 4.

这篇关于CSS3缩放鼠标光标的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆