扒一扒elementUI el-image的源码,我学到了 ... | 源码解析

3,529 阅读5分钟

“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第2篇文章,点击查看活动详情

背景

最近公司遇到一个需求,就是实现一个 图片集的预览,功能包括 放大,缩小,鼠标拖动图片,旋转,恢复原位等
由于ElementUI中的 el-image的预览不符合 业务需求,所以需要自己写
自己看了下源码,并借鉴了过来,此处做个 ElementUIel-image的源码解读,当做学习笔记

稳重代码地址

掘金的在线编辑有点问题,没有展示出来,但是我代码已经写在 script,点击下面框的右上角马上掘金-查看详情 code.juejin.cn/pen/7144598…

先看效果

要实现的效果

业务效果.gif

el-image的效果

ele效果.gif

需求陈述

业务希望图片查看可以固定在页面的左边,图片在左边的一个固定的框里面,图片被限制在这个框里面,图片可以实现 拖动,缩放,旋转等功能。

而右侧是表单操作,业务需要根据左侧看到的图片,并在右侧做相关操作

el-image组件,这些功能都有,但是它是相当于弹框的形式,是个全局弹框的样子,无法满足业务的场景需求

所以我就看了下el-image的预览的源码,比较有感触

虽然在不看源码的情况下,也能自己实现,但是却没有源码实现的那么的 优雅,也不会有很多的细节处理

不得不感叹,平时实现业务代码,能用就行,但是实现同样的功能,代码如何可以写的更好,看了el-image的预览源码,这就是以后需要努力的方向

知识点前置总结

  • 鼠标的按下的事件监听方法 mousedown
  • 鼠标的移动的事件监听方法 mouseover
  • 鼠标的抬起的事件监听方法 mouseup
  • 缩放使用 transform.scale
  • 旋转使用 transform.rotate
  • 移动使用 margin-leftmargin-top

代码逻辑解析

点击鼠标拖动图片

思路

  • 先通过监听 图片的 mousedown的方法
  • 获取起点:记录下鼠标点击图片的那一刻的鼠标的位置,作为移动的起点 e.pageXe.pageY
  • 获取鼠标移动的路径,获取移动的位置,计算出移动的位置与起点的 偏移值,同时改变 图片的margin-leftmargin-top
  • 获取终点:监听mouseup, mouseup事件出发后,就 停止监听 mousemove 方法,最后松开鼠标的点也就是 最后一次 mousemove 执行的值

注意点

  • 监听 mouseupmouseup 事件,必须在 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;
    },
    
  • 其他方面
    源码里面还有一些边界的判断的问题,也做了很多的处理,包括工具的封装等,都是很值得学习的

总结

虽然只是一个简单的小功能,但是人家的源码写的如何简单,并且还做了很好的封装,也做了很好的兼容处理

若是我们自己写,当然也能写出来,但是代码估计就没有这么的简洁,封装没有这么的好,很多兼容或者适配都没有做好

被社区证明的框架源码其实就是最值得我们学习的,说白了就是代码的最佳实践

这么好的学习资源要用起来

而不是整天埋怨没有机会提升,这源码不就摆在这里,而且你还天天在用,多么好的学习机会