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

正常工作中,要显示一个图片,为了性能考虑,肯定是显示它的低分辨率版本。可最近在做的一个项目中,服务器直接提供了原图用来显示,即使只用来当做一个小图标。这导致一个网页消耗的流量达到了近 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, Vue自定义指令

添加新评论