背景说明
某个国际化项目。前端用的技术栈是vue全家桶,使用nuxt-ssr渲染。
项目遇到的问题
项目中用到了大量的产品图片和比较大的banner图片。因为服务器在国外,也没有对静态服务器有相应的配置cdn服务,所以用户在第一次访问的时候,由于没有缓存所以导致图片渲染非常的慢,导致我们网站的体验非常大。
解决问题的想法优化千万条,图片第一条。加载特别慢,用户两行泪。
好吧,我们这次要解决主要就是图片加载慢的问题。有几个客观因素我们暂时无法避免服务器在国外
没有专门的cdn供本项目的使用
既然没有办法从硬件方便入手,那么只能从另外两方面入手图片加载前能否优雅的告诉用户我们正在加载,您不要走,稍等一下
能否加载一些图片质量又高,体积又小的图片来加快图片下载速度
优雅的加载
我们怎么才能在图片下载前,告诉用户我们正在加载呢?可以放一个骨架屏
可以放一个默认图片
可以放个进度条
这些看起来都不错,但是都有一个问题,用户无法感知这个图片的大致内容,或者说这些东西都和原图跳跃性太大。
所以我们选择的是第四种方案:放一个体积在1kb左右特别模糊的小图,然后放大展示。
webp
加载的问题解决了,那么有质量又高,体积又小的图片格式是什么,没错是webp。
关于这个格式的来龙去脉网上有很多介绍,我就不搬砖了(详情见下面两个链接)
根据这些资料,我们可以总结一下webp的特点:同样质量的图片webp格式比jpg格式小50%以上
同样质量的图片webp格式比png格式小45%以上
webkit内核浏览器早就可以支持webp格式图片了
还有个别浏览器(IE:"看我干吗?")对webp支持依然不够友好
所以优化图片的格式就选择了webp。
技术调研
有了上面的思考,我们的想法应该是,不能怂,就是干。
我们来仔细的分析一下上面的这个需求,大致我拆分大两点webp格式图片和小图怎么来的
我怎么把这个功能做的通用一点
图片是怎么来的我不想知道我是怎么没的, 我就想知道我是怎么来的
缺图片?找设计。好吧,如果我们去找设计要这种图片,设计拒绝了我们并且给了一段鄙视语。情理之中,毕竟重复工作谁都不愿意做。
那我们就让机器去做。
具体的想法是,我们能不能在项目打包的时候,顺便把需要的图片都生成出来。那肯定可以,这样的需求看起来有一个东西特别适合来做,webpack的plugin钩子(为什么不用loader?)。简单讲就是,当我们使用本项目的图片的时候比如require('image/bg.jpg')的时候。webpack会触发compiler.hooks的部分声明周期,当我们在打包前生成资源的时候,获取到源图片,然后进行一系列的图片转换,再把转换后的webp和min图片挂载到打包的过程上。这样图片也就被我们造出来了。
功能是怎么被组件化的要做就要做一个脱离业务的组件出来,也就是造个能跑的轮子。
按照正常的逻辑可以先理解一下步骤
对于上面的总结,我们列出个步骤需要三张图片
原图
原图生成的webp格式
原图缩小的min图片
浏览器加载小图片
判断浏览器是否支持webp
如果支持webp,就加载webp图片
如果不支持就加载原图片
将小图片逐渐消失
说明:上面的三张图片我们采用了一个规定:原图片可以是(jpg|jpeg|png)等。
但是webp的图片,就是将原图只是换一个后缀。
缩小的图片则是在原图片的名字后面加入-min
注:当然要支持远程的图片啦,所以还要提供一个修改webp图和mini图片路径的入口,并且增加是否加载这两种图片的判断。
Vue技术栈
好吧,不吐槽vue了,都挺好。我们的技术栈是vue,所以这次组件的设计也是基于vue了。
我们的想法应该对组件有几种用法
class="test"
:src = "require('./images/logo.jpg')"
>
测试地址11123
// 或者
cssStyle={style.container}
className={appStyle.box}
src={require('./assets/bg.jpg')}
>
使用全部功能的组件
cssStyle={style.container}
className={[appStyle.box]}
miniOptions={false}
src={require('./assets/bg.jpg')}
>
不需要加载小图的组
cssStyle={style.container}
webpOptions={false}
src={require('./assets/bg.jpg')}
>
不需要加载webp的组件
cssStyle={style.container}
miniOptions={{
src: src => src
}}
webpOptions={{
src: src => src
}}
src={require('./assets/bg.jpg')}
>
分别设置小图和webp图片的路径
对于上面的参数目前有几个要求
{
cssStyle //接受一个style对象,添加到外层的div上
className // 接受一个字符串或者数组,添加到外层的div上
src // 默认图片路径
miniOptions // 配置小图路径,默认接受一个含有src的对象,src是一个函数,接受处理过的url地址,默认小图的名称是原图后加 -min
webpOptions // 和上面一样,默认webp的图片名称是将后缀修改为 .webp
}
// (miniOptions和webpOptions)上面两个参数,如果不需要则直接设置成false,这样组件就不会加载对应图片,防止404出现
Webpack打包方案
webpack是个极其优秀的打包方案,其中loader可以让这个平台直接加载和优化各种后缀的问题,而plugin更是让海量的想法提供了落地的可能。(webpack如果不熟的一定要好好学一下)
我们相拥webpack做一个这样的plugin
new webpackPluginImageTransformWebpAndMini({
name: '[name]-[hash:8].[ext]',
logger: true,
webpOptions: {
src: src => src.replace(/(?:\.\w+)(\?|$)/, '.webp$1')
},
miniOptions: {
src: src => src.replace(/\.(\w+?)(\?[\s\S]+)?$/,'-min.$1$2'),
resize: {
width: 100
}
},
paths: {
dir: path.resolve(__dirname, './src/assets'),
include: ['bg']
}
})
其中有几个参数要说明一下
- name 非必填项,主要和图片的file-loader或者url-loader采用的规则一样默认是'[name]-[hash:8].[ext]'(是因为vue-cli生成出来的配置是这样的)- paths 必填项,会根据用户所填的路径进行转换(可递归)- 字符串:只是将此路径下的图片进行转换- 对象:{dir:路径include:字符串数组,只是在该路径下指定的文件进行转换exclude:字符串数组,只是在该路径下除去指定的文件进行转换}- logger 是否打印日志,默认是false- webpOptions,对生成的webp的路径进行配置,接受一个函数,参数是图片名称,需要返回一个字符串作为图片的名称。默认只是将图片的后缀修改为wepb{src:src => src}- miniOptions,对生成的小图进行配置,{src:src => src // 和上面的功能一样,默认把图片的名称后面加入-min后缀resize:{// 要压缩的图片大小,如果只写一个,那么默认进行比例,默认width为100width:xxxheight:xxx}}
工程化
有了上面的plugin,我们就可以根据原图片生成对应的小图与webp格式图片。有了这个组件,我们就可以直接使用生成后的图片来完成想要的效果。
下面简单实现一些上面的代码
解决方案
根据上面的使用方案,我们简单实现一些,为了提高一些逼格,下面的代码都是用的ts和tsx来完成的。
组件化延迟背景图方案
组件的都是使用tsx来写的。详细代码就不介绍了,有兴趣的可以看一下ropo。
插件化处理图片生成
上面是简单的目录结构
简单介绍一下:
index.ts
插件入口问题,处理默认的参数,定义apply方法挂载相应的钩子
core.ts
插件核心方法:递归目录
生成mini图片
生成webp图片
判断include和exclude
utils.ts
提供工具方法:判断类型
logger.ts
打印方法
interface.ts
项目中接口规则
各种配置
各种配置提供了ts的简单配置和node端打包的简单配置。
示例
简单的看下图片对比加载过程
vue-dome
头部的简单的效果,贴一下代码
上面就是一个简单的目录,图片就是个背景
vue.config.js
const webpackPluginImageTransformWebpAndMini = require('webpack-plugin-image-transform-webp-and-mini')
const path = require('path')
module.exports = {
configureWebpack: {
plugins: [
new webpackPluginImageTransformWebpAndMini({
paths: {
dir: path.resolve(__dirname, './src/assets'),
include: ['bg']
}
})
]
}
}
main.js
import Vue from 'vue'
import App from './App.vue'
import LazyBackground from 'vue-lazy-background-component'
Vue.use(LazyBackground)
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
App.vue
class="test"
:src = "require('./assets/bg.jpg')"
>
测试地址11123
export default {
name: 'app'
}
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
margin-top: 60px;
}
.test {
width: 540px;
height: 300px;
background-size: cover;
}
运行npm run serve,即可看到头图效果。
运行npm run build,可以看到图片都已经自动生成出来
npm
两个包目前都已经推倒了npm上了,需要的可以上去试试,可以直接用vue-cli生成的项目进行试验
github
如果想看源码的可以在github上,源码都是用ts写的,因此无论是配置还是代码,都是建议看一看
总结
写了这两个东西,学到了很多东西,尤其是webpack。深刻的认识到了,webpack是个后置技能,我们只是在配置文件上简单的配置几项内容而webpack在背后要做很多大量的工作去完成。所以要想学好前端工程,webpack及webpack背后的思想是一定要学习的。
还有一件事:996.icu很无聊。