基于鼠标位置放大图片,但我的数学有些问题

4
我正在制作一个全屏图片查看器,我暂时在这里打开一个开发者URL:

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

该查看器是响应式的,并根据您浏览器的宽度/高度进行缩放。其中一个关键功能是能够使用鼠标滚轮对图像进行缩放。与基于中心的缩放不同,我们根据原点(即鼠标的坐标)进行缩放,从而可以将图像放大到特定区域。

如何再现问题

如果您打开上面的URL并快速尝试一下鼠标滚轮,它可能看起来正常。但是,我使用的数学计算略有偏差。以下是如何再现此问题:

  • 打开上面的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的正确的浏览器前缀。

上述代码实现了基本的缩放功能,没有问题。然而,要实现基于原点的缩放,需要进行更多步骤。在实际缩放之前,我首先使用transform-origin在javascript中设置缩放的原点。以下是我的帮助函数:

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得出可靠的原点百分比。正如问题陈述所示,我已经接近正确的计算结果,但还有些偏差。
尽管缩放目前运行良好,但我希望它能变得完美。这将成为一个非常强大的图像浏览器,也很容易实现,即使对于其他人也是如此。

所以我检查了你对原点的计算,一切都正常。你能解释一下你是如何测量第二个缩放步骤“稍微偏离”的吗? - Ed_
@EdHinchliffe 实际上,我认为它是错误的是通过视觉观察得出的。当我将鼠标定位于图像中的某一点并进行第二次缩放时,缩放结果与我的鼠标位置不符。相反,它似乎在水平和垂直方向上偏移了100个或更多像素。 - Fer
好的,我明白你的问题。让我试着把它用答案说出来! - Ed_
1个回答

1

预期效果

在回答您的问题之前,我认为首先需要澄清预期效果。实际上,您正在寻找与在iPhone上捏合缩放时相同的效果- 捏合的“原点”保持完全相同,而其周围的所有内容都会被拉伸。您可以想象将一些有弹性的织物钉在原点处,并拉动角落。

问题

如果您在缩放之间移动鼠标,则对您而言这很好,但是如果确实移动鼠标,则原点似乎会移动。问题的原因正是如此 - 每次移动鼠标时,您都会更改变换的起点。当然,您确实需要这样做,但是您根据图像的原始(100%缩放)位置计算出起点。实际原点需要位于第一次缩放的原点和新鼠标位置之间。

换句话说,CSS只进行一次转换。如果您将原点设置为x,y,然后缩放到2级,那么这将产生与如果您将原点设置为x2,y2,缩放到1级,然后移动到x,y,然后进入2级的相同结果。

解决方案

我认为您可以通过以下几种方式解决这个问题:
  • 在每次缩放时计算“新”原点的缩放因子
    • 这可能是缩放级别、鼠标位置和先前原点的函数
  • 每次移动原点时计算并应用一个平移
    • 同样取决于当前原点、缩放级别和鼠标位置
  • 找到另一种将变换“堆叠”在一起的方法。
    • 一种方法是在每次操作时动态生成一个新的包含div,并对其应用类似于this question中所接受的解决方案的比例变换。

不幸的是,我没有时间进一步深入研究,但希望这指引您朝着正确的方向前进?


好的,我希望我理解你的意思。我原本以为我已经考虑了图像缩放因素,但你的意思是我考虑了缩放因素,但没有考虑位置与缩放比例之间的关系。换句话说,新的原点应该基于新的图像比例和新的图像位置来计算。这样说对吗? - Fer
是的,我认为是这样的。以您所举的眼睛和喙为例:如果您先放大眼睛再放大喙,然后检查CSS,您会发现其原点与您两次放大喙时相同。 - Ed_
1
谢谢,如果原点相同,那肯定意味着问题在于图像的位置变化,而不是实际的原点。这使得我的正确数学问题仍然存在,但至少现在更清楚了问题所在,感谢您! - Fer
嗯...如果你改变计算原点的方式,当缩放时会改变图像的位置。我的第一个想法是在每个缩放级别保存旧(当前)原点,然后当你缩放时,将新原点设为旧原点和新鼠标位置的平均值。 - Ed_
1
我会花一些时间来权衡努力和实际收益,因为我认为当前的体验至少是合理的。我会将您的答案标记为可接受的,再次感谢。 - Fer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接