likes
comments
collection
share

图片转webp工程化

作者站长头像
站长
· 阅读数 15

图片转webp工程化

一、生成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工程化 经过上述后会自动在目标目录中生成对应的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

这里主要要做两点:

    1. 添加webp父类名,让webp下样式权重更高
.foo {}
.webp .foo {} // 在支持webp的浏览器中该样式权重更高
    1. 经过生成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)
          }
        }
      })
    }
  }
)

转载自:https://juejin.cn/post/7331758803480887348
评论
请登录