前言:

图片过大导致加载变慢是一直以来存在的问题,手动压缩比较繁琐。接下来我们介绍两种通过命令压缩图片的方式

需求描述:

由于项目中图片过大会导致加载慢,需要每次新增图片的时候手动去压缩图片,使用TinyPNG进行图片压缩,每次需要手动上传压缩再下载,比较繁琐,故使用images集成到项目中,执行命令即可

第一种方式:

使用node-image(node轻量级跨平台编码库)

思路

在项目中新建一个文件用来存储图片压缩时间,每次拿到图片的时候获取图片修改时间,然后读取文件中的时间,进行比较,如果大于则进行压缩,小于则不压缩,这样之前压缩过的图片就不会重新压缩,

实现

const images = require("images");
const fs = require("fs");
const path = require("path");
// 图片地址
const IMAGES_PATH = path.resolve(__dirname, `./images`);
/**
 * @description: 进行图片压缩
 * @event: 
 * @param {*} path 要压缩图片的文件夹
 * @return {*}
 */
const compress = (path) => {
  fs.readdir(path, function(err, files){    
    if(err){
        console.log('error:\n' + err);
        return;
    }
    fs.readFile("./imagesMtime.js", "utf-8", function (err, data) {
      files.forEach(function(file){
        fs.stat(path + '/' + file, function(err, stat){
            if(err){console.log(err); return;}
            if(stat.isDirectory()){                
                // 如果是文件夹遍历
                compress(path + '/' + file);
            }else{  
              //遍历图片
              console.log('文件名:' + path + '/' + file);
              var name = path + '/' + file;
              // 获取图片修改时间
              let mtime = fs.statSync(name).mtime;
              mtime = new Date(mtime).getTime();
              if (mtime > data) {
                console.log("压缩了图片")
                images(name).save(name, {
                  quality: 82                    //压缩图片质量
                })
              }
              let date = new Date().getTime();
              fs.writeFile("./imagesMtime.js", date, (err) => {
                console.log("写入")
                console.log(err)
              })
              if (err) {
                console.log(err)
              }
            }              
         });
      });
    })
  });
}
compress(IMAGES_PATH)

执行

可以在终端中直接运行

node 文件名

第二种方式

TinyPNG插件版

思路

在项目中新建一个json文件用来存储过图片路径,下次触发时查看json中是否存在,存在则不再次压缩,不存在则压缩,这样之前压缩过的图片就不会重新压缩,

实现

const fs = require('fs');
const path = require('path');
const https = require('https');
const crypto = require('crypto');
const { URL } = require('url');

const root = './static/',    //process.env.NODE_ENV
  exts = ['.jpg', '.png'],
  max = 5200000; // 5MB == 5242848.754299136

const options = {
  method: 'POST',
  hostname: 'tinypng.com',
  path: '/web/shrink',
  headers: {
    rejectUnauthorized: false,
    'Postman-Token': Date.now(),
    'Cache-Control': 'no-cache',
    'Content-Type': 'application/x-www-form-urlencoded',
    'User-Agent':
      'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'
  }
};
var listStart = fs.statSync('./tinify/list.json');
fileList(root);


// 获取文件列表
function fileList(folder) {
  fs.readdir(folder, (err, files) => {
    if (err) console.error(err);
    files.forEach(file => {
      fileFilter(folder + file);
    });
  });
}

// 过滤文件格式,返回所有jpg,png图片
function fileFilter(file) {
  fs.stat(file, (err, stats) => {
    if (err) return console.error(err);
    if (
      // 必须是文件,小于5MB,后缀 jpg||png
      stats.size <= max &&
      stats.isFile() &&
      exts.includes(path.extname(file))
    ) {
      var stat = fs.statSync(file);   //图片的修改时间
      if(stat.mtimeMs>listStart.mtimeMs){
           let list=[];
           let str=fs.readFileSync( "./tinify/list.json","utf8")  //先读文件
           if(str==""){
             fileUpload(file);  
           }else{
            if(str.indexOf(file)==-1){
               fileUpload(file); 
            }
          }
      }else{

      }
    }
    if (stats.isDirectory()) fileList(file + '/');
  });
}
// 异步API,压缩图片
// {"error":"Bad request","message":"Request is invalid"}
// {"input": { "size": 887, "type": "image/png" },"output": { "size": 785, "type": "image/png", "width": 81, "height": 81, "ratio": 0.885, "url": "https://tinypng.com/web/output/7aztz90nq5p9545zch8gjzqg5ubdatd6" }}
function fileUpload(img) {
  var req = https.request(options, function(res) {
    res.on('data', buf => {
      let obj = JSON.parse(buf.toString());
      if (obj.error) {
        console.log(img+':压缩失败!报错:'+obj.message);
      } else {
      	console.log(img)
      	console.log(obj)
        fileUpdate(img, obj);
      }
    });
  });

  req.write(fs.readFileSync(img), 'binary');
  req.on('error', e => {
    console.error(e);
  });
  req.end();
}
// 该方法被循环调用,请求图片数据
function fileUpdate(imgpath, obj) {
  let options = new URL(obj.output.url);
  let req = https.request(options, res => {
    let body = '';
    res.setEncoding('binary');
    res.on('data', function(data) {
      body += data;
    });

    res.on('end', function() {
      fs.writeFile(imgpath, body, 'binary', err => {
        if (err) return console.error(err);
         var str=[];
         str.push(imgpath);
         var html=JSON.stringify(str)
         fs.writeFile('./tinify/list.json', html, {flag: 'a'}, function (err) {
            if(err) {
                console.error(err);
             } else {
                console.log('写入成功');
             }
          });
        // console.log([${imgpath}] \n '压缩成功,原始大小-'+${obj.input.size}+',压缩大小-'+${obj.output.size}+',优化比例-'+${obj.output.ratio});
      });
    });
  });
  req.on('error', e => {
    console.error(e);
  });
  req.end();
}

两种方式对比

node-images

TinyPNG插件版

费用

免费

每月前500张免费

安全性

安全

会上传到TinyPNG服务端

效果展示

原图:(大小是853kb)

openresty brotli 压缩图片 node 压缩图片_node


node-images压缩后:(大小是188kb)

openresty brotli 压缩图片 node 压缩图片_压缩图片_02


TinyPNG压缩后:(大小是138kb)

openresty brotli 压缩图片 node 压缩图片_压缩图片_03