如题,无关技术背景,什么vue
、react
自己想用就用,这就是js
加一点点css
实现,意思就是可以任意迁移。
效果如下:
加载图片
在加载图片之前我们是需要准备一个容器来存放图片的,图片只能在这个容器里面拖动、缩放,下面就是一个基本的架子,这种代码就没必要解读了吧:
<html>
<head>
<title>Document</title>
<style>
html,
body {
margin: 0;
padding: 0;
}
body {
background: #3c8079;
display: flex;
justify-content: center;
align-items: center;
}
#imgContainer {
width: 500px;
height: 500px;
background: #fff;
position: relative;
overflow: hidden;
}
/* 容器下的图片一些单独的样式 */
#imgContainer img {
position: absolute;
transform-origin: left top;
}
</style>
</head>
<body>
<div id="imgContainer"></div>
</body>
</html>
架子完成就是js
部分了,这里img
肯定是动态加载的,所以创建img
标签,设置一点点属性,然后插入容器中就可以了,代码如下:
const img = document.createElement('img');
img.src = 'https://p26-passport.byteacctimg.com/img/user-avatar/6a646066de6c1376525669efb70295e9~300x300.image';
// 禁止图片拖动
img.draggable = false
img.onload = () => {
const imgContainer = document.getElementById('imgContainer');
imgContainer.append(img);
}
可以看到上面的代码插入的部分写到了img
的onload
事件中了,因为要计算图片的宽高,初始要缩放到容器中完全看见,太小了需要居中,还有宽高比例的问题,还有时候图片太大了,加载出来之后会设置缩放,会有闪动的情况,下面就是处理处理这些:
img.onload = () => {
const imgContainer = document.getElementById('imgContainer');
// 获取容器的宽高
const containerWidth = imgContainer.clientWidth;
const containerHeight = imgContainer.clientHeight;
// 获取图片的宽高
const imgWidth = img.width;
const imgHeight = img.height;
// 获取缩放值
const ratio = getRatio([containerWidth, containerHeight], [imgWidth, imgHeight]);
// 设置缩放
img.style.transform = `scale(${ratio}, ${ratio})`;
// 根据缩放值设置图片的位置,这样能让图片居中显示
img.style.left = (containerWidth * 0.5 - (imgWidth * 0.5 * ratio)) + 'px';
img.style.top = (containerHeight * 0.5 - (imgHeight * 0.5 * ratio)) + 'px';
imgContainer.append(img);
}
/**
* 获取图片和容器的比率
* @param containerSize{Number[]} 容器宽高
* @param imgSize{Number[]} 图片宽高
*/
function getRatio(containerSize, imgSize) {
const [containerWidth, containerHeight] = containerSize;
const [imgWidth, imgHeight] = imgSize;
// 如果图片小于容器就不缩放
if (containerWidth > imgWidth && containerHeight > imgHeight) {
return 1;
}
// 图片宽度更大就返回宽度的比率,否则就高度的
if (imgWidth > imgHeight) {
return containerWidth / imgWidth;
} else {
return containerHeight / imgHeight;
}
}
处理图片拖动
拖动很简单,大概的流程如下:
- 监听鼠标按下事件。
- 获取当前鼠标的坐标
- 监听鼠标移动事件。
- 获取鼠标移动之后的坐标。
- 拖动元素移动。
- 监听鼠标松开事件。
鼠标松开,移除鼠标移动事件和松开事件。
流程都弄清楚就直接开干,上代码:// 1. 监听鼠标按下事件,这个放在容器上面,因为图片会移动,不一定每次都点击在图片上 const imgContainer = document.getElementById('imgContainer'); imgContainer.addEventListener('mousedown', (e) => { // 记录图片初始位置 let posX = parseInt(img.style.left) || 0; let posY = parseInt(img.style.top) || 0; // 2. 获取当前鼠标的坐标 let oldX = e.x - posX; let oldY = e.y - posY; // 3. 监听鼠标移动事件 const mousemove = (e) => { // 4. 获取鼠标移动之后的坐标 const { x, y} = e; // 计算拖动距离 posX = x - oldX; posY = y - oldY; // 5. 拖动元素移动 img.style.left = posX + 'px'; img.style.top = posY + 'px'; }; // 注册在 document 上面就离开这个元素也能拖动 document.addEventListener('mousemove', mousemove); // 6. 监听鼠标松开事件 const mouseup = (e) => { // 7. 鼠标松开,移除鼠标移动事件和松开事件 document.removeEventListener('mousemove', mousemove); document.removeEventListener('mouseup', mouseup) }; // 注册在 document 上面在别的地方松开也会停止拖动事件的监听 document.addEventListener('mouseup', mouseup); });
处理鼠标缩放
鼠标缩放要比拖动要难一些,还是先理一下流程:
- 监听鼠标滚轮滚动事件。
- 往上滚动,放大图片。
- 往下滚动,缩小图片。
流程就三步,感觉比拖动简单很多,不多废话,上代码:
// 1. 监听鼠标滚轮滚动事件
let zoom = 0; // 设置极值
img.addEventListener('wheel', (evt) => {
// 获取图片的缩放数据
const transform = img.style.transform;
const scale = /scale(-?\d+(.\d+)?,\s?-?\d+?(.\d+)?)/g.exec(transform);
if (!scale) return;
// 理论上两个值都是一样的,这里还是写两个,可以对宽高设置不同比例
const regExp = /-?\d+(.\d+)?/g;
let scaleXVal = Number(regExp.exec(scale[0])[0]);
let scaleYVal = Number(regExp.exec(scale[0])[0]);
const radix = 0.1;
if (evt.deltaY > 0) {
// 2. 鼠往下滚动
if (zoom <= 0) return;
zoom--;
scaleXVal -= radix;
scaleYVal -= radix;
} else {
// 3. 鼠标往上滚动
if (zoom >= 20) return;
zoom++;
scaleXVal += radix;
scaleYVal += radix;
}
img.style.transform = `scale(${scaleXVal}, ${scaleYVal})`;
})
上面的代码已经完成了缩放,但是缩放是从左上进行的,我们的目标是沿着鼠标的点进行缩放的。
处理鼠标缩放,根据鼠标坐标点
这里的思路其实很简单(其实自己想了很久),假如图片大小是100px
,放大0.1
倍之后,图片就是110px
,图片是左上角缩放的,那么如果鼠标是在右下角,鼠标的坐标相当于图片来说,放大之前是x: 100 y:100
,放大之后是:x: 110 y: 110
,这个时候只需要让图片向左上偏移10px
,也就是100 * 0.1
。
同理:
如果鼠标在左下角:缩放前坐标:x: 0 y: 100
,缩放后坐标:x: 0 y: 110
;
如果鼠标在左中间:缩放前坐标:x: 0 y: 500
,缩放后坐标:x: 0 y: 55
;
通过上面的规律可得出这样的公式:
(鼠标基于图片的x坐标 / (图片的宽度 * 之前的缩放值)) * (图片的宽度 * 缩放基数)
上面的是宽度,高度也一样,修改后的代码如下:
// 1. 监听鼠标滚轮滚动事件
let zoom = 0; // 设置极值
img.addEventListener('wheel', (evt) => {
// 获取图片的缩放数据
const transform = img.style.transform;
const scale = /scale(-?\d+(.\d+)?,\s?-?\d+?(.\d+)?)/g.exec(transform);
if (!scale) return;
// 理论上两个值都是一样的,这里还是写两个,可以对宽高设置不同比例
const regExp = /-?\d+(.\d+)?/g;
let scaleXVal = Number(regExp.exec(scale[0])[0]);
let scaleYVal = Number(regExp.exec(scale[0])[0]);
// 获取缩放前鼠标基于图片的位置
const ratioX = evt.layerX / (img.width * scaleXVal);
const ratioY = evt.layerY / (img.height * scaleXVal);
// 缩放比例
const radix = 0.1;
// 计算缩放了多少
const scaleX = img.width * radix;
const scaleY = img.height * radix;
// 获取图片的定位
const left = parseInt(img.style.left);
const top = parseInt(img.style.top);
if (evt.deltaY > 0) {
// 2. 鼠往下滚动
if (zoom <= 0) return;
zoom--;
scaleXVal -= radix;
scaleYVal -= radix;
// 缩小偏移是以增加的方式
img.style.left = (left + ratioX * scaleX) + 'px';
img.style.top = (top + ratioY * scaleY) + 'px';
} else {
// 3. 鼠标往上滚动
if (zoom >= 20) return;
zoom++;
scaleXVal += radix;
scaleYVal += radix;
// 缩小偏移是以减少的方式
img.style.left = (left - ratioX * scaleX) + 'px';
img.style.top = (top - ratioY * scaleY) + 'px';
}
// 设置缩放
img.style.transform = `scale(${scaleXVal}, ${scaleYVal})`;
})