CSS3缩放鼠标光标 [英] CSS3 zooming on mouse cursor
问题描述
我的目标是创建一个插件,使缩放&在页面区域上进行平移操作,就像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上的鼠标位置。我认为这可以做你想要的。
我举一些例子来说明:
- example 1:放大和缩小变换原点
- 示例2: zoom on transform-origin&移动缩放框架
- 示例3:示例2 +缩小限制为原始框架边框
- 示例4:示例3 +带有隐藏溢出的父框架< a>
调整 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:
- example 1: zoom in and out on transform-origin
- example 2: zoom on transform-origin & shift zooming frame with translation
- example 3: example 2 + zoom-out limited to original frame borders
- example 4: example 3 + parent frame with hidden overflow
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屋!