示例
简介
以鼠标为中心缩放,根据使用场景不同,分为4种。
- 累加式 先缩放后平移
- 累加式 先平移后缩放
- 非累加式,先缩放后平移
- 非累加式,先平移后缩放
先缩放在平移和先平移再缩放的区别是:重点在平移上,因为ctx.translate的单位是当前缩放倍数的单位。结尾再复述
这里说的累加式,指的是,每次缩放是在上一次的基础上进行缩放,非累加式每次都是在比例为1的基础上进行缩放。
下面将渐进式讲解
非累加式,使用的是ctx.save(),ctx.restore()
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.translate(offset.x, offset.y);// 先平移后缩放
ctx.scale(scale, scale);
...
...
// 绘制图像
ctx.restore();
而累加式是没有使用ctx.save(),即是缩放倍数累计的。
前置知识
ctx.translate: 这个api是移动canvas 中心坐标轴,单位为是当前缩放倍数下的1个容量因子(这个很关键,后面会用到)。
比如缩放倍数为1,ctx.translate(10,10),那么移动为10个容量因子;
缩放倍数为0.5,ctx.translate(10,10),那么移动还是为10个容量因子。
并且是ctx.translate是累加的,把中心坐标轴从上一个位置,移动一段距离。(上面说的非累加式,是因为使用了,ctx.save(),ctx.restore())
ctx.scale(x,y): x水平方向上缩放倍数,y垂直方向上缩放倍数。大于1是放大,小于1是缩小。与ctx.translate一样都是累加的。比如当前倍数是0.5,ctx.scale(1.1,1.1),实际倍数是1.1*0.5,
const {a,d,e,f}=ctx.getTransform() :a:水平缩放因子 d:垂直缩放因子 e:水平平移 f:垂直平移
这里的结果的都是最终结果。比如基础倍数是1,经过2次缩放(1.1倍),a的值为1*1.1*1.1=1.21
这里重点说一下 e和f的值都是1倍下的值,可以说是px,使用式需要转换一下移动了多少个容量因子,e/a
ctx.scale(0.5,0.5); // 缩放0.5
ctx.translate(-100,0) // 100个单位
ctx.strokeStyle = "red";
ctx.beginPath();
ctx.rect(0,0,200,200);
ctx.stroke();
const {a,d,e,f}=ctx.getTransform()
console.log(a,d,e,f,'e,f');
打印结果为:0.5 0.5 -50 0 'e,f'
ctx.translate是移动了100个单位,那么想知道最终移动多少个单位:e/a
原理
累加式
红色为最初状态,倍数为s1,没有进行translate。没有进行translate进行计算是最简单的。 蓝色框为缩放后状态,
上图为实现以鼠标中心缩放,那么L1内的容量因子与L2+L3是相等的,这里的L1长度是鼠标到canvas的距离,就是event.offsetX。
计算L1内总共多少个容量因子
const L1Capacity=L1/s1 // 试想缩放倍数为1,那么L1=100,可以放下100个容量因子,倍数为0.5,可以放入200个容量因子
我们最终需要求的是translate(x,y),就是需要移动多少个容量因子,就是L2。那么先计算出L3在缩放后能容纳多少个因子。总的容量因子减去就可以得出L2需要容量多少个因子。这就是我们要计算的答案,y轴同理 计算L2内总共需要容纳多少容量因子
const s2=(step+1)*s1 //step为0.1或者-0.9 每次缩放为1.1 或者0.9
const L3Capacity=L1/s2 // 在缩放后的状态
const L2Capacity=L1Capacity-L3Capacity
先缩放再平移
ctx.scale(multiple,multiple) // 这里需要填缩放倍数,不是最终结果(s2),multiple为1.1 或者0.9
ctx.translate(-l2Capacity,-l2Capacity)
合并计算
canvas.addEventListener('wheel', function(event) {
let {offsetY,offsetX,wheelDeltaY}=event
event.preventDefault();
let current = 1;
const step=0.1
if(wheelDeltaY>0){
current+=step
// 放大
if(a>=2 )return
}else{
if(a<=0.3 )return
// 缩小
current-=step
}
const {a,d,e,f}=ctx.getTransform() //之前的最终缩放倍数
ctx.scale(current,current)
//offsetX/a对应L1Capacity offsetX/(current*a)对应L3Capacity
let xp=offsetX/a - offsetX/(current*a)
let yp=offsetY/a - offsetY/(current*a)
ctx.translate(-xp,-yp)
}, { passive: false });
大概过程
先平移再缩放
这里就是我踩坑的位置。因为一开始就使用的是累加式的,后面不方便改成非累加式。就硬着头皮计算,捣鼓了半天。
若果先平移那么,在倍数为s1的缩放倍数下,需要移动多少个缩放因子(腾出多少空间),然后s2的时候,可以容纳需要容纳的容量因子,
有一点启发就是,L2是长度是固定的,那么他的长度是多少呢,类比offsetX。L2px=L2CapacityS2*s2=L2CapacityS1*s1,只有一个未知数,可求得L2CapacityS1,那么就是需要在s1的倍数下平移L2CapacityS1,在s2的倍数下才能容量L2CapacityS2容量因子
const s2=(step+1)*s1 //step为0.1或者-0.9 每次缩放为1.1 或者0.9
const L3Capacity=L1/s2 // 在缩放后的状态
const L2Capacity=L1Capacity-L3Capacity
const L2CapacityS1=L2Capacity*s2/s1 = L2Capacity*(step+1)
合并计算
canvas.addEventListener('wheel', function(event) {
let {offsetY,offsetX,wheelDeltaY}=event
event.preventDefault();
let current = 1;
const step=0.1
if(wheelDeltaY>0){
current+=step
// 放大
if(a>=2 )return
}else{
if(a<=0.3 )return
// 缩小
current-=step
}
const {a,d,e,f}=ctx.getTransform() //之前的最终缩放倍数
//(offsetX/a - offsetX/(current*a)) 为需要移动的容量因子 对应L2Capacity
let xp=(offsetX/a - offsetX/(current*a))*current //对应 L2Capacity*(step+1)
let yp=(offsetY/a - offsetY/(current*a))*current
// 先放大在 移动
ctx.translate(-xp,-yp)
ctx.scale(current,current)
}, { passive: false });
对应原本有translate
如果原本就是有translate的呢,就需要考虑e和f。原理还是一样的,鼠标点到2次边界的容量因子是一样多,只需要计算出需要移动的容量因子
现在需要求的就是l6需要容量拿多少容量因子
计算过程 L6=L3+L2-L4-L5
L3 L4 已知
L2 L5 通过e和f转换求得 L3:容量因子:L3/s1 L4:可容量因子L4/s2 L2:容量因子:e/s1 //上面所说的e是px单位,类比offsetX L5:可容纳因子 e/s2
可求得L6
let current = 1;
const {a,d,e,f}=ctx.getTransform()
// console.log(a,d,e,f,'ad');
if(wheelDeltaY>0){
current+=step
// 放大
if(a>=2 )return
}else{
if(a<=0.3 )return
// 缩小
current-=step
}
let xp=((offsetX-e)/a - ((offsetX-e)/(current*a)))
let yp=((offsetY-f)/a - ((offsetY-f)/(current*a)))
先缩放再平移
ctx.scale(current,current)
ctx.translate(-xp,-yp)
先平移再缩放 同理
let xp=((offsetX-e)/a - ((offsetX-e)/(current*a)))*current
let yp=((offsetY-f)/a - ((offsetY-f)/(current*a)))*current
ctx.translate(-xp,-yp)
ctx.scale(current,current)
先平移和后平移的区别:先平移后缩放不同,先平移x,那么平移的是上一个倍数的x个容量因子;后平移是平移当前倍数的x个容量因子
对于使用查看演唱会选票
非累加式,大家可以参考【canvas】react+canvas实现无限画布、鼠标为中心缩放、标尺、移动画布
总结
- 鼠标点到canvas 边界的容量因子是相等的。
- 在吗写代码前还是要构思好,将有关联,有影响的部分考虑清楚