如何通过translate和scale,完成基于鼠标位置的图片缩放?

我是来提问的。
我想完成一个对图片移动、旋转、缩放的功能。因为要打开多个图片,并且不挡住图片后的内容,所以没使用canvassvg

<div>
    <img>
    <img>
    ……
    <img>
</div>

事件都委托到了div上,之前的功能都是靠着改变top、left、width、height四个参数达到目的,后来发现打开图片太多的时候,资源占用太大,就改用了transform。

但通过搜索,发现目前基于鼠标位置的图片缩放都是靠改变top、left、width、height来完成的。
是以左上角的点进行的缩放。
算法基本上是:
(鼠标e.clientX-图片偏移量)/原宽=(鼠标e.clientX-要求得的偏移量)/变化后的宽。

但是上面的方法会过多的消耗资源和对版面的重排重绘,所以选择了css的translatescale来完成功能。
但是上面的算法却无法套用到translatescale上。
scale是以图片的中心点进行缩放,缩放后translate不会变,top、left、width、height也不会变。
难点在于,已经缩放过的图片,在基于鼠标位置的缩放,这个鼠标在的图片的位置坐标和这个偏移量复杂好难计算,我算了2天都是错误的,所以不得不求助大家,希望大家能帮帮忙。

之所以不使用transform-origin设置旋转点,是因为我旋转的功能是靠transform的rotate完成的。如果每次都要改变了旋转点,那么图片的位置也会因为rotate而改变。

至于为什么不用zoom,这个好像不是标准吧,不太懂。如果zoom能更好的节省资源,我会选择,主要是要经常对图片缩放。

阅读 8k
4 个回答

【已解决,答案】
这么多年了,这个问题原来还放着,没人回答。我自己都忘记了(健忘挺严重的),甚至很久没上segmentfault了。

问题发布时间是2017年07月19日,那时候我为了节省资源重构自己的“图片点击放大”油猴脚本,提了这个问题。
2018年1月26日,我解决了这个问题,完成了脚本的重构。

油猴脚本名为“百度贴吧图片点击放大”,地址:https://greasyfork.org/zh-CN/scripts/20969
里面有历史代码版本可以进行对比。
在最新的版本里,我都有对难解的地方进行注释。

结构也如我所提问的一样,是下面这样的,图片之间互不影响:

<div>
    <img class="x">
    <img class="x">
    <img class="x">
</div>

核心思路

无论是我提的问题,还是uihoh0兄弟给出的评论都回答了出来——transform-origin
给每个<img>都设置上一个class,放入{transform-origin: 0 0;}——将图片的变形原点设置为图片的自身左上角。 不使用默认的图片中心为原点,是为了减少繁琐的计算和不必要的思考。(我也不知道为什么当时没想通)

放弃使用top、left、width、height
纯使用transformtranslate, scale, rotate完成图片的移动,放大和旋转。
这部分可以在我发的脚本,代码的wheel事件里看到。

提前先用一个data变量保存好图片的原始{w宽,h高,x偏移量,y偏移量,s缩放倍数,r旋转角度};
原始w宽h高一般是固定的,不动。
x,y,s,r都跟着transform来完成修改,一一对应,不需要额外计算。
每一个动作的完成都是使用data变量里的值,所以完成后也要把修改的值保存到变量里。下次再提出来使用。(计算前先提取,计算后要保存)
然后修改图片transform样式,把之前的所有修改一并写上,不能漏了。(2022年浏览器可以单独修改样式,但为了兼容性目前仍使用transform)

target.style.transform = `translate(${x}px, ${y}px) scale(${s}) rotate(${r}deg)

以鼠标为中心

scale缩放

以鼠标为中心进行缩放,只要计算缩放后的偏移量移过去就可以了。
放到数学里:点A(x1,y1)与点B(x2,y2)偏移量P1。点A放大C倍后得A11(x11,y11),点B放大C倍后得B22(x22,y22),他们的偏移量为P2=P1*C。
不是单纯的使用缩放量套入,而是计算缩放比例:缩放后的量/缩放前的量。
比如图片原始是1倍的scale,你自己设置增量是0.2,放大一次那就是1.2scale,计算就是tmp=1.2/1.0。再放大一次就是1.4scale,计算tmp=1.4/1.2。
再然后计算图片的x和y的偏移量。

x = e.clientX - (e.clientX - x) * tmp; // 以鼠标位置进行缩放。e.clientX-x为鼠标与图片x坐标系(即x偏移量)的距离,*tmp为缩放后的距离,e.clientX-计算得相对鼠标缩放后移动的新坐标系

如果换成图片中心为原点,计算是麻烦的,因为左上角的坐标缩放后会变,而不变的是中心坐标。需要多几步换算。再加上之后的旋转,那更加麻烦了。

rotate旋转

已知数学:在平面坐标上,任意点(x1,y1),绕一个坐标点(x2,y2)逆时针旋转A角度后,新的坐标为(x, y)的计算公式:
x= (x1 - x2)*cos(A) - (y1 - y2)*sin(A) + x2 ;
y= (x1 - x2)*sin(A) + (y1 - y2)*cos(A) + y2 ;

tmp = 0.01745329 * A; // 1角度=0.017453293弧度,A角度转为弧度计算(因为Math.cos用的弧度)。
x = (x - e.clientX) * Math.cos(tmp) - (y - e.clientY) * Math.sin(tmp) + e.clientX; // 以鼠标位置(e.clientX,e.clientY)为中心,坐标系(x,y)旋转tmp弧度,计算新坐标系
y = (x - e.clientX) * Math.sin(tmp) + (y - e.clientY) * Math.cos(tmp) + e.clientY;

其中要注意的点在于是使用逆时针公式还是顺时针公式。
rotate旋转逆时针会成为负数不好计算,所以需要图片顺时针旋转计算。
以鼠标为中心,表示点不动,图片顺时针旋转表示以图片左上角为原点的坐标轴顺时针旋转,此时使用逆时针公式。
那如何计算逆时针的度数?一个圆360°,逆时针10°不就是顺时针350°吗。

鼠标图片移动

鼠标拖拽

这部分的逻辑就跟使用top、left移动图片一样。
先保存按下鼠标时的数据,拖动时改变样式transform,鼠标放开后将修改的数值保存到data变量里。
稍微比top、left麻烦的是三个动作里都需要用到{x偏移量,y偏移量,s缩放倍数,r旋转角度},虽然s和r不变,但是修改样式transform需要用到他们。

rotate旋转后的非鼠标移动

非鼠标移动指不是通过按下移动放开鼠标这一串动作完成的移动图片,比如使用滚轮或者点按钮上上下下。
没看到有人说和使用。(百度贴吧原生的看图模式里,有这种使用场景,使用了rotate旋转,但是用的top、left,数值变化方式正常没有影响。)
为什么特意说呢,因为rotate旋转后,translate计算的量和方式不一样。
不过,这种场景没多少人用,所以不过多说明。
想了解的可以看看我的代码,里面有注释。一看就能理解(所谓“上下颠倒,反着来”)。

感觉其实你多嵌套一层来处理rotate的transform-origin,就可以用transform-origin来设置放大中心点了啊...

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
宣传栏