CSS3变焦上的鼠标光标(CSS3 zooming on mouse cursor)

2019-06-26 07:44发布

我的目标是创建一个插件,使缩放和页面上的区域平移操作,随便怎么样谷歌地图目前工程(意思是:用鼠标滚动= /缩小区域的缩放,点击并按住与移动和释放=平移)。

滚动时,我希望有集中在鼠标光标变焦操作。

对于这一点,我用即时CSS3矩阵变换。 唯一的,但具有强制性,约束条件是,我不能用任何东西比CSS3转换和规模转变,以0像素0像素的转换原点。

平移是我的问题的范围,因为我有它已经工作。 当谈到变焦,我在努力揣摩出故障是在我的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);
}

Answer 1:

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

我会用transform-origin (和浏览器兼容性的姐妹属性)将变焦调整到您所缩放DIV鼠标位置。 我觉得这个可以做你想做的。 我把一些示例上捣鼓说明:

  • 例1: 放大和缩小变换原点
  • 实施例2: 放大上变换原点移位及与翻译缩放框
  • 实施例3: 实施例2 +缩小限于原始帧边界
  • 实施例4: 具有隐藏溢出实施例3 +父框架

调整transform-origin

所以在applyTransformations你的功能,我们可以调节transform-origin从动态imageXimageY ,如果我们从通过这个值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);

(在此第一小提琴例子我只是用你的translateXtranslateYTransformations传递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);
    }

在这个例子中我调整了原来的剧本多一点,并添加第二小提琴例子 。

现在我们放大和缩小从任意缩放级别的鼠标光标。 但由于移我们最终走动原来的div(“测量地球”)......这看起来很滑稽,如果你用有限的宽和高(放大的对象在一端,缩小工作另一端,我们向前推进像尺蠖)。

避免了“尺蠖效应”

为了避免这种情况,你可以例如添加限制,使得左图像边界不能移动到其原始x的右侧坐标,顶部图像边界不能移动比其原始y位置较低,所以对其他两个边界。 但随后的放大/缩小不会完全光标,同时也得到了图像的边缘结合的(你会发现图像滑动入位)在实施例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实施例4 。



Answer 2:

我们做了这个反应库: 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

一个主花样被正确计算初始指针/鼠标按下状态,并且当触摸/鼠标移动发生的当前状态之间的差异。 当按下鼠标时,捕获的坐标。 然后在每一个鼠标移动(直到鼠标向上)计算的距离差异。 这是DIFF你需要,以确保你的光标下的初始点是变焦的焦点,以抵消翻译什么。



文章来源: CSS3 zooming on mouse cursor