标签 Vue自定义指令 下的文章

上一篇文章的自定义指令非常简陋,使用场景仅限于静态的、不再改变的图片,如果用在列表中,会因为没有处理 update 事件,图片不会更新。

在原来的代码中,依赖原图片的 onload 事件获取原图像的尺寸,并最终用新生成的图片替换了原图片,导致自定义指令的 updated 钩子传入的参数 el 在 DOM 里已经是不存在了,也就没办法找到并更新这个图片了。

由于上述的缺点,本篇文章将换一种思路,不再替换原图像,而是替换原图像的 src,也不再从原图像的 onload 事件中获取其尺寸,而是获取原图像的 src,新创建一个 img 元素,在这个新 img 的 onload 事件中获取尺寸。

在 Vue 的自定义指令中,如果只需要关注 mounted 和 updated 钩子,那么可以使用简写形式,即直接返回一个函数。本篇文章就使用了简写的形式,新的自定义指令代码如下:

export default (el, binding, vnode) => {
    const _img = document.createElement('img');
    _img.crossOrigin = 'anonymous';
    _img.onload = () => {
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');
        const thumbWidth = binding.value ?? 50;
        const thumbHeight = _img.naturalHeight * thumbWidth / _img.naturalWidth;
        canvas.width = thumbWidth;
        canvas.height = thumbHeight;

        try {
            ctx.drawImage(_img, 0, 0, thumbWidth, thumbHeight);
        } catch (error) {
            console.error(error);
            return;
        }

        el.src = canvas.toDataURL();
    };
    _img.src = vnode.props.src;
}

代码比原来简单得多。在使用上也比原来简单,不需要添加 crossorigin 属性了:

<img v-thumb :src="xxxx" >

End

这个自定义指令本不应该存在。

正常工作中,要显示一个图片,为了性能考虑,肯定是显示它的低分辨率版本。可最近在做的一个项目中,服务器直接提供了原图用来显示,即使只用来当做一个小图标。这导致一个网页消耗的流量达到了近 100MB,并严重拖累页面性能,连鼠标的 hover 样式都延迟很久才会响应。

可惜奇葩的后端目前没有解决这个问题的打算,产品同学也觉得这样还是很 OK,即使解决这个问题很容易——就是在生成图片的时候多生成几个不同分辨率的版本。

下载网页资源消耗的是公司服务器和用户的流量,自然不需要操心,也不是前端同仁能够管得了的。显然用户用起这个产品来并没有感到不适,公司也不在乎服务器多消耗的那点流量。但页面性能变差,作为一个前端老师傅,这能忍?本文的自定义指令正是用来解决这个问题的,正所谓改变不了别人,还改变不了自己么?

既然图片太大拖累了页面性能,那么在图片下载完毕之后,生成一个低分辨率的版本替换它,问题应该就解决了。代码很简单,全部贴上来。因为才疏学浅,肯定有考虑不周的地方(实际上没有考虑),请不吝指教。

//新建一个 thumb.js
export default {
    mounted(el, binding) {
        el.onload = () => {
            //同是天涯沦落人
            if (el.src.startsWith('data:image')) return;

            const canvas = document.createElement('canvas');
            const ctx = canvas.getContext('2d');
            //默认宽度给50是不是有点小气了?
            const thumbWidth = binding.value ?? 50;
            //naturalHeight 和 naturalWidth 只有在 onload 之后才能获取到
            const thumbHeight = el.naturalHeight * thumbWidth / el.naturalWidth;
            canvas.width = thumbWidth;
            canvas.height = thumbHeight;
            ctx.drawImage(el, 0, 0, thumbWidth, thumbHeight);

            //将原来图片上的属性全复制过去,除了 src
            const attrs = el.attributes;
            const img = document.createElement('img');
            img.src = canvas.toDataURL();
            //如果你不喜欢 for 循环遍历 NamedNodeMap,还能用 Array.prototype.forEach.call
            for (let i = 0; i < attrs.length; i++) {
                const attr = attrs[i]; //这是一个 Attr 对象
                if (attr.name === 'src') continue;
                img.setAttribute(attr.name, attr.value);
            }

            if (el.parentNode) {
                el.parentNode.replaceChild(img, el);
            }
        };
    }
};

注意你不能直接替换原图片的 src 属性,因为这会循环触发 onload 事件。

使用这个自定义指令:

import vThumb from 'thumb.js'; //在 setup 中,以 v开头的驼峰形式命名的变量会自动注册为指令
<!-- 注意跨域的问题,如果图片不允许跨域,那么半天白忙活了 -->
<img v-thumb src="beauty.jpg" crossorigin="anonymous">
<img v-thumb="100" src="beauty.jpg" crossorigin="anonymous">

使用的时候还要注意它的局限性,例如并没有处理绑定到原图片上的事件。


End

Vue官方文档:https://cn.vuejs.org/v2/guide/custom-directive.html

什么是Vue自定义指令?

Vue自定义指令用起来就像Vue内置的v-modelv-on...之类,只不过实现了一些自己想要的功能。因为Vue自定义指令的钩子函数会传入一个el,它是自定义指令绑定到的那个DOM元素,所以Vue自定义指令可以用来直接和DOM打交道。

如何注册一个指令?

自定义指令分为全局和组件内的局部指令。全局自定义指令注册方法:

Vue.directive('myDirective', {
    // inserted是内置的钩子函数
    inserted: function (el) {
        // 一些操作...
    },
    // 指令的其他内容和钩子函数,如果需要的话...
})

- 阅读剩余部分 -