最终实现效果如下图
第一版
-
最开始接到这个需求的时候,想的很简单,感觉不是一个很复杂的东西
-
开始想的是用
transform-origin
和transform:scale
实现 -
根据鼠标位置来改变缩放中心,滚轮滚动来改变
scale
大小 -
代码如下
js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background:
-webkit-linear-gradient(top, transparent 99px, #ccc 99px),
-webkit-linear-gradient(left, transparent 99px, #ccc 99px);
background-size: 100px 100px;
}
.container {
position: relative;
top: 0;
left: 0;
width: 100px;
height: 100px;
}
img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
transform-origin: 0% 0%;
}
</style>
</head>
<body>
<div class="container">
<img id="img" src="https://t7.baidu.com/it/u=1653814446,2847580380&fm=193&f=GIF" alt="">
</div>
<script>
let s = 1
let minL = 1
let maxL = 20
const img = document.getElementById('img')
img.addEventListener('mousewheel', (e) => {
const { x, y, deltaY } = e
s += deltaY * -0.01
s = Math.min(Math.max(minL, s), maxL)
img.style.transformOrigin = `${x}px ${y}px`
img.style.transform = `scale(${s})`
})
</script>
</body>
</html>
-
乍一看,貌似这个功能就这样实现了
-
但是,当我们在一个位置缩放后,鼠标换个位置再次缩放,图片就会瞬移,如果缩放位置在最初的盒子的位置之外,图片还可能缩放到视图之外,
-
想不通瞬移,百度,查,查到一个老哥遇到了和我相同的问题这是他的帖子,在他文章回复里看到了瞬移的原因
- 还有解决方案
-
继续改代码,计算
scale,translate
......, -
算不对,老是算不对,还是算不对,不算了,百度
-
百度出来怎么做的都有,就是有点看不懂.....,虽然有些看不懂,但还是吸收了一点经验
第二版
- 既然做不出来,那就问问水友群的大佬吧,
-
第二版就这样出来了,用
transform:scale
来控制大小,top,left
控制图片位置 -
我加了一点自己思考,让这个问题变得更加容易理解
1.每次缩放的时候可以获取鼠标位置(x,y
)
2.获取鼠标位置相对于图片缩放为1(transform:scale(1)
)时的位置(x2,y2
)
3.图片从scale=1
缩放到scale=s
后,鼠标的位置(x3,y3
)
4.再从缩放后的位置(x2,y2
),回到(x,y
)的位置,计算出图片的偏移(x4,y4
)
5.设置图片位置和缩放比例
js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background:
-webkit-linear-gradient(top, transparent 99px, #ccc 99px),
-webkit-linear-gradient(left, transparent 99px, #ccc 99px);
background-size: 100px 100px;
}
.container {
position: absolute;
top: 0;
left: 0;
width: 200px;
height: 200px;
}
img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
transform-origin: 0% 0%;
}
</style>
</head>
<body>
<div class="container">
<img id="img" src="https://t7.baidu.com/it/u=1653814446,2847580380&fm=193&f=GIF" alt="">
</div>
<script>
let s = 1//缩放倍率
let lasts = 1
let minL = 1
let maxL = 20
const img = document.getElementById('img')
// 缩放前,鼠标位置相对图片缩放为1时的位置
const getSourePosition = (el, s, x, y) => {
let sourceTop = +el.style.top.slice(0, -2)
let sourceLeft = +el.style.left.slice(0, -2)
let x2 = (x - sourceLeft) / s
let y2 = (y - sourceTop) / s
return { x2, y2 }
}
// 缩放后,图片需要的位移
getXY = (s, x, y, x2, y2) => {
// 缩放后的位置
let x3 = x2 * s
let y3 = y2 * s
// 缩放后的位置移动到鼠标位置,需要的位移
let x4 = x - x3
let y4 = y - y3
return { x4, y4 }
}
// 设置图片位置
const setPosition = (el, x4, y4, s) => {
el.style.left = `${x4}px`
el.style.top = `${y4}px`
el.style.transform = `scale(${s})`
}
img.addEventListener('mousewheel', (e) => {
const { x, y, deltaY } = e
s += deltaY * -0.01
s = Math.min(Math.max(minL, s), maxL)
// 放大
if (deltaY < 0) {
let { x2, y2 } = getSourePosition(img, s - 1, x, y)
let { x4, y4 } = getXY(s, x, y, x2, y2)
if (lasts < maxL) {
lasts = s
setPosition(img, x4, y4, s)
}
}
// 缩小
if (deltaY > 0) {
let { x2, y2 } = getSourePosition(img, s + 1, x, y)
let { x4, y4 } = getXY(s, x, y, x2, y2)
if (lasts > minL) {
lasts = s
setPosition(img, x4, y4, s)
}
}
})
</script>
</body>
</html>
第三版
-
第二版其实已经实现了我们所需要的功能
-
但是每次缩放我们都需要去获取,设置图片的
left
和top
, -
left
和top
属性的改变都会触发了布局操作,开销高。更好的解决方案是在元素上使用translate
,因为它不会触发重新布局。
js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background:
-webkit-linear-gradient(top, transparent 99px, #ccc 99px),
-webkit-linear-gradient(left, transparent 99px, #ccc 99px);
background-size: 100px 100px;
}
.container {
position: absolute;
top: 0;
left: 0;
width: 200px;
height: 200px;
}
img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
transform-origin: 0% 0%;
transform: translate(0px, 0px) scale(1);
}
</style>
</head>
<body>
<div class="container">
<img id="img" src="https://t7.baidu.com/it/u=1653814446,2847580380&fm=193&f=GIF" alt="">
</div>
<script>
let s = 1//缩放倍率
let lasts = 1
let minL = 1
let maxL = 20
const img = document.getElementById('img')
const getTransLate = (transform) => {
let tx = 0, ty = 0
if (transform === '') {
return { tx, ty }
} else {
tx = transform.split(' ')[0].replaceAll('translate(', '').replaceAll('px,', '')
ty = transform.split(' ')[1].replaceAll('px)', '')
return { tx, ty }
}
}
// 缩放前,鼠标位置相对图片缩放为1时的位置
const getSourePosition = (el, s, x, y) => {
let { tx, ty } = getTransLate(el.style.transform)
let x2 = (x - tx) / s
let y2 = (y - ty) / s
return { x2, y2 }
}
// 缩放后,图片需要的位移
getXY = (s, x, y, x2, y2) => {
// 缩放后的位置
let x3 = x2 * s
let y3 = y2 * s
// 缩放后的位置移动到鼠标位置,需要的位移
let x4 = x - x3
let y4 = y - y3
return { x4, y4 }
}
// 设置图片位置
const setPosition = (el, x4, y4, s) => {
el.style.transform = `translate(${x4}px, ${y4}px) scale(${s})`
}
img.addEventListener('mousewheel', (e) => {
const { x, y, deltaY } = e
s += deltaY * -0.01
s = Math.min(Math.max(minL, s), maxL)
// 放大
if (deltaY < 0) {
let { x2, y2 } = getSourePosition(img, s - 1, x, y)
let { x4, y4 } = getXY(s, x, y, x2, y2)
if (lasts < maxL) {
lasts = s
setPosition(img, x4, y4, s)
}
}
// 缩小
if (deltaY > 0) {
let { x2, y2 } = getSourePosition(img, s + 1, x, y)
let { x4, y4 } = getXY(s, x, y, x2, y2)
if (lasts > minL) {
lasts = s
setPosition(img, x4, y4, s)
}
}
})
</script>
</body>
</html>
总结
-
网上大佬的实现都是直接计算缩放后的tanslate
-
我的实现是先将鼠标位置映射到缩放为1时,再缩放s倍,再回到鼠标位置
-
初始将图片缩放中心设为
0px 0px
会节省一些计算 -
transform-origin
不要动