标签: svg2png

  • 给 Hexo 增加替换大图并生成缩略图的功能

    给 Hexo 增加替换大图并生成缩略图的功能

    我厂的官方博客使用 Hexo 搭建,静态页比较符合我厂的技术风格。

    我厂博客可能会用到一些很大的火焰图 SVG,厂长担心打开速度会受拖累,所以让我想办法把大图替换成缩略图,单击再打开原图。

    经过一些研究,我大概得到以下线索:

    1. Hexo 会加载 `/scripts` 下面的脚本
    2. Hexo 是用钩子机制,跟 WordPress 非常像
    3. Hexo 的钩子函数可以使用 Promise

    那么,大概方案就出来了:

    1. 添加脚本,等待 `after_post_render` 钩子。这个钩子触发的时候,博文已经从 markdown 渲染成 html。
    2. 使用 cheerio,找到所有图片,检查图片大小,略过不太大的图片(我的标准是 500K)
    3. 对大图生成缩略图,替换 `src`。

    因为目标是 SVG => PNG/JPG,在 npm 里搜了一圈,发现大家不是 phantom.js 就是 puppeteer,那我就不需要用库了,自己直接写好了。

    经过反复的尝试,最终完成的代码如下:

    hexo.extend.filter.register('after_post_render', async function (data) {
      /** 摘录、详情、内容 */
      const dispose = ['excerpt', 'more', 'content'];
      for (const key of dispose) {
        const $ = cheerio.load(data[key], {
          ignoreWhitespace: false,
          xmlMode: false,
          lowerCaseTags: false,
          decodeEntities: false
        });
    
        // 获取所有需要调整大小的图片路径
        let images = $('img').map(async function () {
          let src = $(this).attr('src');
          if (!src) {
            if (config.log) {
              console.info("no src attr, skipped...");
              console.info($(this));
            }
            return;
          }
    
          // 顺便加上 `loading="lazy"`
          $(this).attr('loading', 'lazy');
    
          // take snapshot fo big SVGs
          const originalSrc = src;
          if (/\.svg$/.test(src)) {
            const img = resolve(__dirname, '../source', originalSrc);
            const {size} = await stat(img);
            if (size <= config.limit) {
              return;
            }
            $(this)
              .attr('data-src', src)
              .attr('src', `${src}.png`);
            return img;
          }
        }).get();
        images = await Promise.all(images);
        images = images.filter(image => !!image);
        // 只启动一次 pupppeteer,希望减少系统消耗
        if (images.length > 0) {
          const browser = await puppeteer.launch({
            defaultViewport: config,
          });
          const page = await browser.newPage();
          for (const image of images) {
            await page.goto(`file://${image}`);
            const buffer = await page.screenshot();
            const name = basename(image);
            console.log('screenshot for svg: ' + name);
            // 这一步很重要,因为 `after_post_render` 的时候图片还没有复制,所以要利用 hexo 自身的复制功能复制图片
            route.set(`images/${name}.png`, buffer);
          }
          await browser.close();
        }
        data[key] = $.html();
      }
      return data;
    });

    这段代码里,搞清楚应该用 route.set() 稍微花了些时间,Hexo 的文档写的真是差强人意。这个方法可以向指定位置写入内容,写入的可以是文本,也可以是二进制 buffer。如果写入的是绝对路径,那么就是简单的写入;如果是相对路径,就是向博文环境内写入,接下来的复制过程也会复制到最终代码里。