当鼠标移动时,跟随光标缩放图像会出错。

22

这是对 如何在使用自己的鼠标滚轮平滑滚动时缩放到鼠标指针 的后续问题。

我正在使用CSS transforms将图像缩放到鼠标指针。 我还使用自己的平滑滚动算法来插值并为鼠标滚轮提供动量。

通过之前问题中Bali Balo的帮助,我已经完成了90%的工作。

现在您可以将图像完全缩放到鼠标指针,同时仍然具有平滑滚动效果,如下JSFiddle所示:

http://jsfiddle.net/qGGwx/7/

但是,当移动鼠标指针时,功能会受到破坏。

进一步说明,如果我在鼠标滚轮上放大一个刻度,那么图像会围绕正确的位置进行缩放。每次我在鼠标滚轮上缩小一个刻度时,此行为都会继续发生,完全按照预期。但是,如果在缩放的过程中,我将鼠标移动到不同的位置,则功能会受到破坏,我必须完全缩小以更改缩放位置。

预期的行为是在缩放过程中鼠标位置的任何更改都能正确地反映在缩放后的图像中。

控制当前行为的两个主要函数如下:

self.container.on('mousewheel', function (e, delta) {
        var offset = self.image.offset();

        self.mouseLocation.x = (e.pageX - offset.left) / self.currentscale;
        self.mouseLocation.y = (e.pageY - offset.top) / self.currentscale;

        if (!self.running) {
            self.running = true;
            self.animateLoop();
        }
        self.delta = delta
        self.smoothWheel(delta);
        return false;
    });

该函数收集缩放图像当前比例下鼠标的当前位置。

然后启动我的平滑滚动算法,从而导致对每个插值调用下一个函数:

zoom: function (scale) {

    var self = this;
    self.currentLocation.x += ((self.mouseLocation.x - self.currentLocation.x) / self.currentscale);
    self.currentLocation.y += ((self.mouseLocation.y - self.currentLocation.y) / self.currentscale);

    var compat = ['-moz-', '-webkit-', '-o-', '-ms-', ''];
    var newCss = {};
    for (var i = compat.length - 1; i; i--) {
        newCss[compat[i] + 'transform'] = 'scale(' + scale + ')';
        newCss[compat[i] + 'transform-origin'] = self.currentLocation.x + 'px ' + self.currentLocation.y + 'px';
    }
    self.image.css(newCss);


    self.currentscale = scale;
},

这个功能接受缩放量(1-10),并应用CSS变换,使用transform-origin重新定位图像。

尽管当图像完全缩小时选择静止鼠标位置时,它可以完美地工作;但如上所述,在部分缩放后移动鼠标光标会破坏此功能。

非常感谢能够提供帮助的任何人。


演示根据您的描述似乎运行良好。请详细说明确切的问题。您所说的“正确反映”是什么意思? - Eliran Malka
缩放比例设置为1-10。尝试通过鼠标滚轮将其缩放到一半,例如约3左右。然后将鼠标移动到另一个像素上,并继续缩放其余部分。您会发现它不会正确缩放到新的鼠标位置。当整个缩放期间保持鼠标指针静止时,缩放原点仅保持正确位置,当鼠标移动时它应该正确地更改位置。 - gordyr
好的,我明白了,所以“正确”的行为是缩放到新指定的位置(根据当前比例尺级别提取)而不是在比例尺1中缩放到相对位置。我理解得对吗? - Eliran Malka
确切地说...非常抱歉没有表达清楚。 - gordyr
5个回答

8
实际上并不太复杂。您只需要将鼠标位置更新逻辑与缩放更新逻辑分开。请查看我的fiddle: http://jsfiddle.net/qGGwx/41/ 我在容器上添加了一个“mousemove”侦听器,并将self.mouseLocation更新逻辑放在那里。由于它不再需要,因此我还从“mousewheel”处理程序中删除了鼠标位置更新逻辑。动画代码保持不变,以及何时开始/停止动画循环的决定。
以下是代码:
    self.container.on('mousewheel', function (e, delta) {
        if (!self.running) {
            self.running = true;
            self.animateLoop();
        }
        self.delta = delta
        self.smoothWheel(delta);
        return false;
    });

    self.container.on('mousemove', function (e) {
        var offset = self.image.offset();

        self.mouseLocation.x = (e.pageX - offset.left) / self.currentscale;
        self.mouseLocation.y = (e.pageY - offset.top) / self.currentscale;
    });

谢谢jordancpaul,这非常接近了。然而,尽管新的缩放位置现在是正确的,但整个东西似乎远离鼠标指针移动,这意味着缩放并没有精确地匹配鼠标所在位置。很抱歉这有点难以表达。你提供了到目前为止最接近的答案,但我会再让它保持一段时间,以防有人能够得到完美的答案,如果没有,我将授予你答案。无论如何,做得很好,谢谢! - gordyr

5
在您查看此处的代码片段之前,我应该提醒您:

首先,在您的.zoom()方法中,您不应该除以currentscale
self.currentLocation.x += ((self.mouseLocation.x - self.currentLocation.x) / self.currentscale);
self.currentLocation.y += ((self.mouseLocation.y - self.currentLocation.y) / self.currentscale);

因为在initmousewheel()方法中计算mouseLocation时,已经使用了该因素,例如:

self.mouseLocation.x = (e.pageX - offset.left) / self.currentscale;
self.mouseLocation.y = (e.pageY - offset.top) / self.currentscale;

因此,在.zoom()方法中,你应该:
self.currentLocation.x += (self.mouseLocation.x - self.currentLocation.x);
self.currentLocation.y += (self.mouseLocation.y - self.currentLocation.y);

但是(例如)a += b - a 总会产生 b,所以上面的代码等同于:
self.currentLocation.x = self.mouseLocation.x;
self.currentLocation.y = self.mouseLocation.y;

简而言之:

self.currentLocation = self.mouseLocation;

那么,似乎你甚至不需要 self.currentLocation。(两个变量表示同一个值)。为什么不在设置 transform-origin 的行中使用 mouseLocation 变量,同时摆脱 currentLocation 变量呢?

newCss[compat[i] + 'transform-origin'] = self.mouseLocation.x + 'px ' + self.mouseLocation.y + 'px';

其次,你应该在initmousewheel()方法中包括一个mousemove事件监听器(就像其他开发人员在这里建议的那样),但它应该连续更新转换,而不仅仅是当用户滚动时。否则,在你放大“任何”随机点时,指针的尖端将永远无法追上。
self.container.on('mousemove', function (e) {
    var offset = self.image.offset();
    self.mouseLocation.x = (e.pageX - offset.left) / self.currentscale;
    self.mouseLocation.y = (e.pageY - offset.top) / self.currentscale;
    self.zoom(self.currentscale);
});

因此,在 mousewheel 事件处理程序中,您将不再需要计算这个值,因此,您的 initmousewheel()方法将如下所示:

initmousewheel: function () {
    var self = this;

    self.container.on('mousewheel', function (e, delta) {
        if (!self.running) {
            self.running = true;
            self.animateLoop();
        }
        self.delta = delta;
        self.smoothWheel(delta);
        return false;
    });

    self.container.on('mousemove', function (e) {
        var offset = self.image.offset();
        self.mouseLocation.x = (e.pageX - offset.left) / self.currentscale;
        self.mouseLocation.y = (e.pageY - offset.top) / self.currentscale;
        self.zoom(self.currentscale); // <--- update transform origin dynamically
    });
}

一个问题:

这个解决方案按预期工作,但有一个小问题。当用户以正常或快速的速度移动鼠标时,mousemove事件似乎会错过最终位置(在Chrome中测试)。因此,缩放将略微偏离指针位置。否则,当您慢慢移动鼠标时,它会获取到精确点。不过,这应该很容易解决。

其他注意事项和建议:

  • 您有一个重复的属性(prevscale)。
  • 我建议您始终使用JSLintJSHint(也可以在jsFiddle上使用)来验证您的代码。
  • 我强烈建议您在可能的情况下使用闭包(通常称为立即调用函数表达式(IIFE))避免全局范围,并隐藏内部/私有属性和方法。

1
太棒了!!!非常精彩的答案,值得获得奖励。您对代码本身的评论是完全正确的,我很感激,尽管 jsFiddle 本身并不是我在应用程序中使用的实际代码,它只是我为演示目的编写的一个快速简化模型,因此在翻译中存在一些疏漏。无论如何,我真的应该养成使用 jSFiddle 中提供的 JSLint 的习惯。再次感谢您。 :) - gordyr
没问题,很乐意帮忙。 :) - Onur Yıldırım

4

添加一个mousemover方法,并在init方法中调用它:

mousemover: function() {
    var self = this;
    self.container.on('mousemove', function (e) {

        var offset = self.image.offset();

        self.mouseLocation.x = (e.pageX - offset.left) / self.currentscale;
        self.mouseLocation.y = (e.pageY - offset.top) / self.currentscale;

        self.zoom(self.currentscale);
    });
},

Fiddle: http://jsfiddle.net/powtac/qGGwx/34/


谢谢powtac...恐怕这个方法不起作用,Fiddle也是如此。行为仍然保持不变。然而,在mousemove事件中跟踪鼠标位置的变化似乎是一个有趣的路线。干杯。 - gordyr
啊,或许你误解了问题,我很抱歉。当鼠标移动时,图像不应该移动,它只应该随着鼠标滚轮变化/缩放...当鼠标位置改变时保持不动,然后如果用户继续进一步缩放,则从新的鼠标位置恢复缩放。希望这样能澄清问题...感谢你的尝试! - gordyr

3
缩放图片(比率为0.9)导致缩放点不完全正确,实际上鼠标指向的是容器中特定的点,但是我们需要缩放图像。请参见这个示例 http://jsfiddle.net/qGGwx/99/ ,我在其中添加了位置等于变换原点的标记。可以看到如果图像大小与容器大小相同,则没有问题。您需要对此进行缩放吗?也许您可以添加第二个容器?在示例中,我还在mousemove中添加了条件。
if(self.running && self.currentscale>1 && self.currentscale != self.lastscale) return;

这样可以防止在缩放过程中图像移动,但也会出现一个问题。如果zoom仍在运行,则无法更改缩放点。


谢谢abc667。不幸的是,由于图像大小事先无法确定并且通过CSS进行缩放以填充屏幕,因此需要进行缩放。有没有其他解决方法? - gordyr
我进行了大量测试,事实上,没有问题。在这个fiddle的http://jsfiddle.net/qGGwx/102/中,我添加了第二张图片,并将绿色标记的位置设置为变换原点,这样它每次都可以正常运行。如果您从“scale=1”缩放到某个点,则此点不会居中。如果您更改缩放点,则会发生相同的情况,看起来像错误,但实际上并非如此。如果您想使其看起来“正确”,则必须根据距离中心的距离来确定缩放点的中心。但是,如果您将其缩放到某个角落,您将得到容器的3/4为黑色。 - abc667

3

在扩展@jordancpaul的答案中,我添加了一个常量mouse_coord_weight,它被乘以鼠标坐标的增量。这旨在使缩放过渡对鼠标坐标的变化不那么敏感。请查看http://jsfiddle.net/7dWrw/

我已经重写了onmousemove事件处理程序:

    self.container.on('mousemove', function (e) {
        var offset = self.image.offset();
        console.log(offset);

        var x = (e.pageX - offset.left) / self.currentscale,
            y = (e.pageY - offset.top) / self.currentscale;

        if(self.running) {
            self.mouseLocation.x += (x - self.mouseLocation.x) * self.mouse_coord_weight;
            self.mouseLocation.y += (y - self.mouseLocation.y) * self.mouse_coord_weight;
        } else {
            self.mouseLocation.x = x;
            self.mouseLocation.y = y;
        }

    });

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