“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第2篇文章,点击查看活动详情”
背景
最近公司遇到一个需求,就是实现一个 图片集的预览,功能包括 放大,缩小,鼠标拖动图片,旋转,恢复原位等
由于ElementUI中的el-image的预览不符合 业务需求,所以需要自己写
自己看了下源码,并借鉴了过来,此处做个ElementUI的el-image的源码解读,当做学习笔记
稳重代码地址
掘金的在线编辑有点问题,没有展示出来,但是我代码已经写在 script,点击下面框的右上角马上掘金-查看详情
code.juejin.cn/pen/7144598…
先看效果
要实现的效果
el-image的效果
需求陈述
业务希望图片查看可以固定在页面的左边,图片在左边的一个固定的框里面,图片被限制在这个框里面,图片可以实现 拖动,缩放,旋转等功能。
而右侧是表单操作,业务需要根据左侧看到的图片,并在右侧做相关操作
而el-image组件,这些功能都有,但是它是相当于弹框的形式,是个全局弹框的样子,无法满足业务的场景需求
所以我就看了下el-image的预览的源码,比较有感触
虽然在不看源码的情况下,也能自己实现,但是却没有源码实现的那么的 优雅,也不会有很多的细节处理
不得不感叹,平时实现业务代码,能用就行,但是实现同样的功能,代码如何可以写的更好,看了el-image的预览源码,这就是以后需要努力的方向
知识点前置总结
- 鼠标的按下的事件监听方法
mousedown - 鼠标的移动的事件监听方法
mouseover - 鼠标的抬起的事件监听方法
mouseup - 缩放使用
transform.scale - 旋转使用
transform.rotate - 移动使用
margin-left和margin-top
代码逻辑解析
点击鼠标拖动图片
思路
- 先通过监听 图片的
mousedown的方法 - 获取起点:记录下鼠标点击图片的那一刻的鼠标的位置,作为移动的起点
e.pageX和e.pageY - 获取鼠标移动的路径,获取移动的位置,计算出移动的位置与起点的 偏移值,同时改变 图片的
margin-left和margin-top - 获取终点:监听
mouseup,mouseup事件出发后,就 停止监听mousemove方法,最后松开鼠标的点也就是 最后一次mousemove执行的值
注意点
- 监听
mouseup和mouseup事件,必须在mousedown的回调函数里面。大白话就是:在鼠标点击在 图片的时候,才算拖动图片
这个时候 才开始监听 鼠标的移动和鼠标放开(因为存在一个情况:即使不按下鼠标,鼠标就在图片上面滑动,mousemove也是触发的,但那不是我们想要的,所以要避免这种情况) mousedown的事件回调必须e.preventDefault();也就是阻止冒泡,这个很好理解,这个拖动事件不需要传递到外层
代码如下
<img class="img-box__item" @mousedown="handleMouseDown" :src="url" alt="" :style="imgStyle">
handleMouseDown(e){
console.log('handleMouseDown ===',e)
const { offsetX, offsetY } = this.transform;
// 起点
const startX = e.pageX;
const startY = e.pageY;
// rafThrottle 防抖事件
this._dragHandler = rafThrottle(ev => {
this.transform.offsetX = offsetX + ev.pageX - startX;
this.transform.offsetY = offsetY + ev.pageY - startY;
});
// on 是封装的监听事件
on(document, 'mousemove', this._dragHandler);
on(document, 'mouseup', ev => {
// off 是封装的解除监听事件
off(document, 'mousemove', this._dragHandler);
});
e.preventDefault();
},
缩放,旋转
说明
旋转,可以通过点击按钮事件触发
缩放, 需要通过按钮触发,并且也可以通过 鼠标的滚轮实现
思路
- 点击按钮(旋转),触发事件,直接改变
image的样式中的transform.rotate - 点击按钮(缩放),触发事件,直接改变
image的样式中的transform.scale - (鼠标滚动实现缩放):页面初始化的时候,监听
mousewheel,根据回调函数,判断 滚轮是 上滚还是下滚,然后给一个固定的缩放 ,改变transform.scale
代码如下
handleActions(action, options = {}) {
const { zoomRate, rotateDeg, enableTransition } = {
zoomRate: 0.2,
rotateDeg: 90,
enableTransition: true,
...options
};
const { transform } = this;
switch (action) {
// 缩小
case 'zoomOut':
if (transform.scale > 0.2) {
transform.scale = parseFloat((transform.scale - zoomRate).toFixed(3));
}
break;
// 变大
case 'zoomIn':
transform.scale = parseFloat((transform.scale + zoomRate).toFixed(3));
break;
// 顺时针旋转
case 'clocelise':
transform.deg += rotateDeg;
break;
// 逆时针旋转
case 'anticlocelise':
transform.deg -= rotateDeg;
break;
}
transform.enableTransition = enableTransition;
},
滚轮缩放
this._mouseWheelHandler = rafThrottle(e => {
const delta = e.wheelDelta ? e.wheelDelta : -e.detail;
if (delta > 0) {
this.handleActions('zoomIn', {
zoomRate: 0.13,
enableTransition: false
});
} else {
this.handleActions('zoomOut', {
zoomRate: 0.13,
enableTransition: false
});
}
});
// on 是封装的监听事件
on(document, mousewheelEventName, this._mouseWheelHandler);
细节处理
-
监听
mousemove一定是在mousedown的回调方法里面,也就是 必须鼠标按下图片之后才可以拖动 -
监听
mouseup也是一样也是在mousedown的回调方法里面,道理同上 -
监听 鼠标滚轮的事件 做了 兼容 ,
firefox中与其他浏览器有所不同
const mousewheelEventName = isFirefox() ? 'DOMMouseScroll' : 'mousewheel'; -
mousemove方法的回调,做了优化 ,在每次 浏览器页面 重绘的时候,处理mousemove的回调函数,不仅仅起到了防抖的作用,而且在UI展现上面,不会有损失。function rafThrottle(fn) { let locked = false; return function(...args) { if (locked) return; locked = true; window.requestAnimationFrame(_ => { fn.apply(this, args); locked = false; }); }; } -
通过
transform来控制缩放与旋转,通过margin来控制拖动,最后直接把style直接挂到img的组件上 说实话,我刚开始自己想的是,通过原生操作DOM来不断的用js设置style,虽然也能实现,但是没有这个简练imgStyle() { const { scale, deg, offsetX, offsetY, enableTransition } = this.transform; const style = { transform: `scale(${scale}) rotate(${deg}deg)`, transition: enableTransition ? 'transform .3s' : '', 'margin-left': `${offsetX}px`, 'margin-top': `${offsetY}px` }; return style; }, -
其他方面
源码里面还有一些边界的判断的问题,也做了很多的处理,包括工具的封装等,都是很值得学习的
总结
虽然只是一个简单的小功能,但是人家的源码写的如何简单,并且还做了很好的封装,也做了很好的兼容处理
若是我们自己写,当然也能写出来,但是代码估计就没有这么的简洁,封装没有这么的好,很多兼容或者适配都没有做好
被社区证明的框架源码其实就是最值得我们学习的,说白了就是代码的最佳实践
这么好的学习资源要用起来
而不是整天埋怨没有机会提升,这源码不就摆在这里,而且你还天天在用,多么好的学习机会