4

我正在使用全屏图像查看器,我将在此处临时打开一个开发 URL:

http://www.jungledragon.org/apps/jd3/image/704/great_grey_owl.html/zoom

此查看器具有响应性,可根据您的浏览器宽度/高度进行缩放。它的主要功能之一是能够使用鼠标滚轮放大和缩小图像。这个想法不是基于中心的缩放,而是基于原点进行缩放,即鼠标的坐标,允许您放大图像的特定区域。

如何重现问题

如果您打开上面的 URL 并快速使用鼠标滚轮,它可能看起来工作正常。但是,我使用的数学有点偏离。以下是重现该问题的方法:

  • 打开上面的网址
  • 将鼠标悬停在猫头鹰的左眼上
  • 使用鼠标滚轮缩放一步,它应该完全放大到眼睛
  • 将鼠标放在猫头鹰的喙上
  • 使用鼠标滚轮再放大一步

您现在应该注意到,第二个缩放步骤并没有完全进入猫头鹰的喙,它似乎在水平和垂直方向上都略微偏离了。我认为这是数学不好的结果。

这个怎么运作

这是处理这一切的javascript:

http://www.jungledragon.org/apps/jd3/js/jd3-slideshow.js

我正在捕获鼠标滚轮事件。根据它的方向,我正在增加或减少缩放级别。实际的缩放只不过是应用一个使用 CSS3 变换来缩放图像的 CSS 类:

&.grow1 { @include jd-scale(1); }
&.grow2 { @include jd-scale(1.5); }
&.grow3 { @include jd-scale(2.0); }
&.grow4 { @include jd-scale(2.5); }
&.grow5 { @include jd-scale(3.0); }

注意:上面是对 SASS mixin 的调用,它转换为transform:scale的正确供应商前缀。

以上完成了基本的缩放没有问题。但是,要使基于原点的缩放成为可能,还需要几个步骤。在进行实际缩放时,我首先使用 javascript 设置缩放的原点transform-origin。这是我设置它的辅助函数:

function zoomOrigin(selector, originStr) {
selector.css({'-webkit-transform-origin': originStr});
selector.css({'-moz-transform-origin': originStr});
selector.css({'-ms-transform-origin': originStr});
selector.css({'-o-transform-origin': originStr});
selector.css({'transform-origin': originStr});
}

这个问题的核心是计算正确的原点。计算这个值有两点值得一提:

  • 绝对坐标(即 X 和 Y)是相对于图像的,而不是相对于页面的
  • 原点的计算应该考虑到图像已经根据当前缩放状态放大/缩小

原点计算基于 mousemove 事件实时发生。这是这样做的方法,删除了不相关的部分:

$("#image-container img").mousemove(function(e) {

// user has moved their mouse. in case of zooming or panning, this means that the
// origin (center point) of those interactions need to be recalculated

// calculate the mouse offset within the zoomable object (which is different than the page-level offset)
// this relies on the parent of the element having position:relative set
var parentOffset = $(this).offset();
zoomOriginX = e.pageX - parentOffset.left;
zoomOriginY = e.pageY - parentOffset.top;

// recalculate the width and height of the image given the current zoom level       
width = $(this).outerWidth() + (1 + ((zoomLevelCurrent - 1)*0.5) * $(this).outerWidth());
height = $(this).outerHeight() + (1 + ((zoomLevelCurrent - 1)*0.5) * $(this).outerHeight());

// calculate origin percentages based on zoomed width and height
// the zoom methods rely on these variables to be set
zoomOriginPercX = (zoomOriginX / width * 100);
zoomOriginPercY = (zoomOriginY / height * 100);
});

该方法的主要目的是正确设置全局变量zoomOriginPercXzoomOriginPercY,它们用于在缩放之前设置原点(百分比)。

从数学的角度来看,我的想法是简单地计算图像的缩放宽度,并使用偏移 X 和 Y 来得出可靠的原点百分比。正如问题陈述所示,我非常接近正确的计算,但有些东西不对劲。

尽管缩放目前效果很好,但我希望它是完美的。它将成为一个非常强大的图像查看器,它非常容易实现,也适用于其他人。

4

1 回答 1

1

Desired Effect

To start answering your question I think it's worth first clarifying the desired effect. Essentially you're looking for the same effect you'd get if you pinched to zoom on an iPhone - the 'origin' of the pinch stays exactly the same, and everything around it stretches. You can imagine pinning some stretchy fabric at the origin, and pulling the corners.

Problem

This is working fine for you if you don't move the mouse between zooms, but if you do, the origin appears to move. The cause of the problem is exactly that - you are changing the origin of the transform every time you move the mouse. Of course you do need to do this, but you are calculating the origin based on the original (100% zoomed) position of the image. The actual origin needs to be somewhere between the origin of the first zoom and the new mouse position.

In other words, CSS is just doing one transform. If you set the origin to x,y then zoom to zoom level 2, this will give the same result as if you set the origin to x2,y2, zoom to level 1, then move to x,y, and go to level 2.

Solutions

I presume you could solve the issue in several ways:

  • Calculate a scaling factor for the 'new' origin on each zoom
    • this is likely a function of zoom level, mouse position and previous origin
  • Calculate and apply a translation each time the origin is moved
    • again will depend on the current origin, zoom level and mouse position
  • Find another way to 'stack' transforms on top of one another.
    • One way to do this may be to dynamically generate a new containing div each time you and apply a scale transform to that similar to the accepted solution in this question.

Unfortunately I don't have the time to go further than this, but hopefully it points you in the right direction?

于 2013-05-21T09:28:33.280 回答