一、生成webp图片
1. husky中触发转化脚本的指令
// .husky/pre-commit
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
current_branch=`git rev-parse --abbrev-ref HEAD`
if [[ $current_branch != 'master' && $current_branch != 'release' ]]; then
# 生成 webp 图片
npm run webp -- commit
fi
2. 获取一切有改变的图片
const { execSync } = require('child_process');
function getChangedImgs() {
return String(execSync('git status -s'))
.split('n')
.filter(item => item.charAt(1) !== 'D') // 过滤掉首字母为 'D' 的项
.map(item => item.slice(2).trim()); // 获取空格后的文件途径
.filter(path => path.match(/.(jpe?g)|(png)/))
}
coonst imgFiles = getChangedImgs()
3. 使用插件生成webp
const imagemin = require("imagemin");
const imageminWebp = require("imagemin-webp");
// 转化
function transformWebp(dir, filePath) {
return imagemin([filePath ? filePath : `${dir}/*.{jpg,png}`], {
destination: `${dir}`,
plugins: [
imageminWebp({
quality: 75, // 图片质量
})
]
});
};
经过上述后会主动在目标目录中生成对应的webp文件;
4. node同步履行函数主动提交新生成的webp图片
const addFile = imgFiles
.map(path => {
return path.replace(/.(png|jpe?g)$/, ".webp");
})
.filter(webpPath => {
return fs.existsSync(webpPath);
});
execSync(`git add ${addFile.join(" ")}`);
二、运用webp图片
1. 支撑webp的浏览器增加webpclass类
function supportsWebP() {
const elem = document.createElement('canvas');
if (!!(elem.getContext && elem.getContext('2d'))) { // 判别浏览器是否支撑 canvas
return elem.toDataURL('image/webp').indexOf('data:image/webp') === 0;
// 创立一个 WebP 图片并检查其 data URL 是否以 'data:image/webp' 最初
}
return false; // 浏览器不支撑 canvas
}
if (supportsWebP()) {
// 在根元素html上增加class webp
document.documentElement.classList.add('webp')
}
2. 核心功能-处理css
这儿主要要做两点:
-
- 增加webp父类名,让webp下款式权重更高
.foo {}
.webp .foo {} // 在支撑webp的浏览器中该款式权重更高
-
- 经过
生成webp图片
后,图片库中默许生成了全套的webp图;咱们需求将css中引入的png图悉数替换成webp图; 因为是同途径同文件名,不同后缀,只需求替换后缀即可。
- 经过
.foo {
background: url('./img/bg.png') center / contain no-repeat
}
.webp .foo {
background: url('./img/bg.webp') center / contain no-repeat
} // 在支撑webp的浏览器中该款式权重更高
- 3.上述第2点需求注意一个点,并非一切的图片转化成webp后都会更小,所以咱们在是否增加.webp类时需求做一个判别;
参考 咱们这儿用的是替换文件后缀,一起判别原文件和webp谁更小而决定是否运用webp
const postcss = require('postcss')
const path = require('path');
const fs = require('fs');
const propReg = /^background/
module.exports = postcss.plugin(
'postcss-webp',
({
webpClass = 'webp',
pattern = /.(png|jpe?g)/,
cssModules = false,
ignoreComment = 'webp-ignore',
includes = [],// 正则只处理部分模块
excludes = [],// 排除模块
// url-loader 的 limit 配置。base64 的已经内嵌的其他资源了,没有必要再用 webp
limit = 4096,
} = {}) => {
return (root) => {
const { file: filePath, css: content } = root.source.input;
const webpReg = new RegExp(`.${webpClass}\s[a-z#\.\*]`)
if (content && webpReg.test(content)) return;
const isInclude = includes.length ? includes.some((reg) => reg.test(filePath)) : true;
const isExclude = excludes.length ? excludes.some((reg) => reg.test(filePath)) : false;
if (!isInclude || isExclude) return;
root.walkRules((rule) => {
const ruleIgnore = rule.parent.nodes.filter((el) => el.type === 'comment' && el.text === ignoreComment)
if (ruleIgnore.length) return
if (rule.selector.indexOf(`.${webpClass}`) !== -1) return
const hasBackground = rule.nodes.filter((el) => {
return el.type === 'decl' && el.prop.match(propReg)
})
if (hasBackground) {
const webpRule = postcss.rule({
selector: cssModules ? `:global(.${webpClass}) ${rule.selector}` : `.${webpClass} ${rule.selector}`
})
let useWebp = false;
rule.walkDecls(propReg, (decl) => {
const declIgnore = decl.next() && decl.next().type === 'comment' && decl.next().text === ignoreComment
if (declIgnore) return;
const hasUrl = decl.value.match(/url(([^)]*)?)/)
if (hasUrl && pattern.test(decl.value)) {
const imageUrl = hasUrl[1].replace(/'|"/gi, '').replace(/?.*$/, '');
const imagePath = path.join(path.dirname(filePath), imageUrl);
const webpPath = imagePath.replace(pattern, '.webp');
const imgStat = fs.statSync(imagePath);
const webpStat = fs.existsSync(webpPath) ? fs.statSync(webpPath) : null
const imageFullUrl = hasUrl[1].replace(/'|"/gi, '')
if (imgStat.size > limit && webpStat && webpStat.size < imgStat.size && !/?nowebp/.test(imageFullUrl)) {
useWebp = true;
webpRule.append({
prop: decl.prop,
value: decl.value.replace(pattern, '.webp')
})
}
} else {
webpRule.append({
prop: decl.prop,
value: decl.value,
});
}
});
if (webpRule.nodes.length && useWebp) {
rule.after(webpRule)
}
}
})
}
}
)