Greasy Fork

Greasy Fork is available in English.

For Imhentai

不翻墙下,更快加载 imhentai.xxx 的图片

当前为 2023-06-13 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         For Imhentai
// @namespace    http://tampermonkey.net/
// @version      1.3
// @description  不翻墙下,更快加载 imhentai.xxx 的图片
// @author       水母
// @match        https://imhentai.xxx/gallery/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        none
// @require      https://cdn.bootcdn.net/ajax/libs/jszip/3.10.1/jszip.min.js
// @require      https://cdn.bootcdn.net/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js
// @license MIT
// ==/UserScript==

(function () {
  'use strict';

  // Your code here...
  // 全局数据
  let IS_DEBUG = false;
  let IS_INIT = false;
  let IS_RUN = false;
  let IS_DOWNLOADING = false;
  // 页码序列 [cover.jpg, 1.jpg, ..., 30.jpg] : cover不计入页数; eg.总页数定为 30; 数组 [0] 定为 cover
  let CURRENT_PAGE = 0; // 当前页码
  let MAX_BROWSE_PAGE = 0; // 最大浏览的页码,只增
  let PAGE_LOADED = 0; // 已加载的页码
  let IS_FREE_SCALE = false; // 自由缩放
  let SCALE = IS_DEBUG ? 0.5 : 1; // 图片整体缩放
  // 用户定义的下载页码区间
  let UserCustemRange = {
    min: 0,
    max: 0,
    page_loaded: 0,
  };
  // enum
  const BtnID = {
    runBtn: 'run',
    previousBtn: 'pre',
    nextBtn: 'next',
    downloadBtn: 'down',
    scaleUpBtn: 'sUp',
    scaleResetBtn: 'sReset',
    scaleDownBtn: 'sDown',
  };
  const BtnText = {
    runBtn: '启动🥰',
    previousBtn: '⫷',
    nextBtn: '⫸',
    downloadBtn: '下载🥵',
    scaleUpBtn: '⇱',
    scaleResetBtn: '↺◲',
    scaleDownBtn: '⇲',
  };
  /**
   * 异步 FileReader 加载完毕计算器
   */
  const CounterForFileReader = {
    is_lock: false,
    count: 0,
    /**
     * 如果更新成功,返回 true
     * @returns {boolean}
     */
    update() {
      if (this.is_lock) return false;
      else {
        this.is_lock = true;
        this.count++;
        this.is_lock = false;
        return true;
      }
    },
  };

  /**
   *
   * @param {string} name_en
   * @param {string} name_sub
   * @param {number} page
   * @param {string} root_url
   * @param {ImgInfo[]} imgInfoList
   * @param {string[]} types
   */
  function BzData(
    name_en = 'Null',
    name_sub = 'Null',
    page = 0,
    root_url = '',
    imgInfoList = [],
    types = ['.jpg', '.png', '.gif', '.err']
  ) {
    this.name_en = name_en;
    this.name_sub = name_sub;
    this.page = page;
    this.root_url = root_url;
    this.imgInfoList = imgInfoList;
    this.types = types;
  }
  /**
   *
   * @param {string} imgName
   * @param {string} imgAlt
   * @param {string} imgUrl
   * @param {string} imgType
   * @param {number} width
   * @param {number} height
   * @param {number} SCALE
   * @param {Image} imgObj
   * @param {string} imageBase64
   */
  function ImgInfo(
    imgName,
    imgAlt,
    imgUrl = '',
    imgType = '',
    width = 0,
    height = 0,
    SCALE = 1,
    imgObj = null,
    imageBase64 = ''
  ) {
    this.imgName = imgName;
    this.imgAlt = !imgAlt ? imgName : imgAlt;
    this.imgUrl = imgUrl;
    this.imgType = imgType;
    this.width = width;
    this.height = height;
    this.SCALE = SCALE;
    this.imgObj = imgObj;
    this.imageBase64 = imageBase64;
  }
  /**
   * BzData 迭代器
   * @param {BzData} bzData
   */
  function* BzDataIterator(bzData) {
    let index = 0;
    while (index < bzData.imgInfoList.length) {
      let imgInfo = bzData.imgInfoList[index];
      yield [index++, bzData.root_url, imgInfo];
    }
  }

  /**
   * 漫画名去特殊字符处理
   * @param {string} filename 文件名
   * @return {string} 处理后的文件名
   */
  function processFilename(filename) {
    return filename
      .replaceAll('\\', '-')
      .replaceAll('/', '-')
      .replaceAll(':', ':')
      .replaceAll('*', '-')
      .replaceAll('?', '?')
      .replaceAll('"', '“')
      .replaceAll('<', '《')
      .replaceAll('>', '》')
      .replaceAll('|', '~');
  }

  /**
   * 判断图片 url 有效与否
   * @returns {Promise<Image>}
   */
  function verifyImgExists(imgUrl) {
    return new Promise((resolve, reject) => {
      let ImgObj = new Image();
      ImgObj.src = imgUrl;
      ImgObj.onload = () => resolve(ImgObj);
      ImgObj.onerror = (rej) => reject(rej);
    });
  }

  /**
   * 为 ImgInfo 测试三种后缀
   * @param {string} root_url
   * @param {ImgInfo} imgInfo
   * @param {string[]} [types=['.jpg', '.png', '.gif', '.err']]
   */
  async function processImgInfoAsync(
    root_url,
    imgInfo,
    types = ['.jpg', '.png', '.gif', '.err']
  ) {
    // 测试三种后缀
    for (let type of types) {
      imgInfo.imgUrl = root_url + imgInfo.imgName + type;
      imgInfo.imgType = type;
      try {
        let ImgObj = await verifyImgExists(imgInfo.imgUrl);
        imgInfo.imgObj = ImgObj;
        imgInfo.width = ImgObj.width;
        imgInfo.height = ImgObj.height;
        break;
      } catch (e) {
        continue; // 未测试最后一个,继续
      }
    }
  }

  /**
   * 为所有图片生成正确后缀类型
   * @param {BzDataIterator} bzDataIterator
   */
  async function processImgAsync(bzDataIterator) {
    for (let [index, root_url, imgInfo] of bzDataIterator) {
      await processImgInfoAsync(root_url, imgInfo);
      PAGE_LOADED = index;
      if (IS_DEBUG) console.log(`${index}:`, imgInfo);
    }
  }

  /**
   * 循环数据,直至所有图片的 imageBase64 加载完全
   * @param {BzData} bzData
   * @param {number} min
   * @param {number} max
   */
  const watchImgInfoAsync = async (bzData, min, max) => {
    console.log('watchImgInfoAsync');
    document.querySelector(`#${BtnID.downloadBtn}`).textContent = 'Waiting...';

    let is_done = false;
    let intervalID = setInterval(
      (bzData, is_done) => {
        if (IS_DEBUG) console.log('watchImgInfoAsync');
        // 检查是否加载完全
        for (let index = max; index >= min; index--) {
          if (bzData.imgInfoList[index].imageBase64 === '') break;
          else is_done = true;
        }
        // 更新进度
        let percentage = Math.round(
          (CounterForFileReader.count / (max - min + 1)) * 100
        );
        if (IS_DEBUG) console.log(`进度: ${percentage}`);
        document.querySelector(
          `#${BtnID.downloadBtn}`
        ).textContent = `Loading ${percentage}%`;

        if (is_done) {
          // 下载开始
          clearInterval(intervalID);
          downloadZip(bzData, min, max);
        }
      },
      1500,
      bzData,
      is_done
    );
  };

  /**
   * 获取图片的 base64 编码
   * @param {BzData} bzData
   * @param {number} min
   * @param {number} max
   */
  const getImageBase64Async = async (bzData, min, max) => {
    for (let i = min; i <= max; i++) {
      if (bzData.imgInfoList[i].imageBase64 !== '') continue;
      try {
        let reader = new FileReader();
        reader.onloadend = function () {
          bzData.imgInfoList[i].imageBase64 = reader.result;
          // 持续,直至更新计数
          let intervalID = setInterval(() => {
            if (CounterForFileReader.update()) clearInterval(intervalID);
          }, Math.round(Math.random() * 1000));

          if (IS_DEBUG)
            console.log(
              `[getImageBase64Async:${i}:${
                bzData.imgInfoList[i].imgName
              }].imageBase64: ${bzData.imgInfoList[i].imageBase64.substring(
                0,
                22
              )}...`
            );
        };

        // 加载图片的 blob 类型数据
        if (bzData.imgInfoList[i].imgType !== '.err') {
          let imgBlob = await fetch(bzData.imgInfoList[i].imgUrl).then(
            (respone) => respone.blob()
          );
          reader.readAsDataURL(imgBlob); // 将 blob 数据转换成 DataURL 数据
        } else {
          reader.readAsDataURL(new Blob()); // 空文件
        }
      } catch (e) {
        console.error(e);
      }
    }
  };

  /**
   * 批量下载图片
   * @param {BzData} bzData 图像数据
   * @param {number} min
   * @param {number} max
   */
  const downloadZip = async (bzData, min, max) => {
    document.querySelector(`#${BtnID.downloadBtn}`).textContent = '打包';

    if (IS_DEBUG) console.log('downloadZip');

    const zip = new JSZip();
    // 图片 url json 文件,去除 imageBase64 数据
    let bzDataUser = {
      ...bzData,
      imgInfoList: bzData.imgInfoList.map((imgInfo) => {
        return { ...imgInfo, imageBase64: '' };
      }),
    };
    let stringData = JSON.stringify(bzDataUser, null, 2);
    zip.file(`${bzData.name_en}.json`, stringData);
    // 图片 zip
    const fileFolder = zip.folder(bzData.name_en); // 创建 bzData.name_en 文件夹
    const fileList = [];
    for (let i = min; i <= max; i++) {
      let name = bzData.imgInfoList[i].imgName + bzData.imgInfoList[i].imgType;
      if (IS_DEBUG) name = `${i}.${name}`; // 使用重复图片,避免文件名相同覆盖
      let imageBase64 = bzData.imgInfoList[i].imageBase64.substring(22); // 截取 data:image/png;base64, 后的数据
      fileList.push({ name: name, img: imageBase64 });
    }
    // 往 zip 中,添加每张图片数据
    for (let imgFile of fileList) {
      fileFolder.file(imgFile.name, imgFile.img, {
        base64: true,
      });
    }
    zip.generateAsync({ type: 'blob' }).then((content) => {
      saveAs(content, bzData.name_en + '.zip');
    });

    // 按钮还原
    document.querySelector(`#${BtnID.downloadBtn}`).textContent =
      BtnText.downloadBtn;
    IS_DOWNLOADING = false;
  };

  /**
   * 数据初始化,获取漫画名、页数、图片的 url
   */
  function initData() {
    let bzData = new BzData();
    let coverUrl;
    // cover
    bzData.imgInfoList.push(new ImgInfo('cover'));
    const tag_div_main = document.querySelectorAll(
      'body > div.overlay > div.container > div.row.gallery_first > div'
    );
    // 获取漫画名

    bzData.name_en = tag_div_main[1].querySelector('h1').textContent;
    bzData.name_sub = tag_div_main[1].querySelector('p.subtitle').textContent;
    // 漫画名去特殊字符处理
    if (bzData.name_sub !== '') {
      bzData.name_sub = processFilename(bzData.name_sub);
    }
    if (bzData.name_en !== '') {
      bzData.name_en = processFilename(bzData.name_en);
    } else {
      bzData.name_en = bzData.name_sub;
    }

    // 获取页数
    let page_str = tag_div_main[1].querySelector('li.pages').textContent;
    bzData.page = Number.parseInt(page_str.match(/Pages: ([0-9]*)/i)[1]);

    // 图片序列的 url 前缀与封面 url 的相同,
    // eg.封面 url=https://m7.imhentai.xxx/023/mnsiote3jg/cover.jpg
    // eg.序列的 url=https://m7.imhentai.xxx/023/mnsiote3jg/
    coverUrl = tag_div_main[0].querySelector('img').dataset.src;
    bzData.root_url = coverUrl.slice(0, coverUrl.lastIndexOf('/') + 1);

    // 图片序列的 url 生成,
    // eg: https://m6.imhentai.xxx/021/fh5n1d304g/1.jpg
    for (let p = 1; p <= bzData.page; p++) {
      bzData.imgInfoList.push(new ImgInfo(p.toString())); // 图片名未编码,数字序列就行
    }

    let bzDataIterator = BzDataIterator(bzData);
    // 初始化 cover 数据,让 CURRENT_PAGE 与 PAGE_LOADED 能够对齐
    let [index, root_url, coverInfo] = bzDataIterator.next().value;
    let ImgObj = new Image();
    ImgObj.src = coverUrl;
    ImgObj.onload = () => {
      coverInfo.width = ImgObj.width;
      coverInfo.height = ImgObj.height;
    };
    coverInfo.imgUrl = coverUrl;
    coverInfo.imgType = coverUrl
      .substring(coverUrl.lastIndexOf('.'))
      .toLowerCase();

    if (IS_DEBUG) console.log(coverInfo);
    return [bzData, bzDataIterator];
  }

  /**
   * 初始化组件
   * @param {BzData} bzData
   * @param {BzDataIterator} bzDataIterator
   */
  function initComponents(bzData, bzDataIterator) {
    // <img>
    const newImg = document.createElement('img');
    newImg.id = 'can-img';
    newImg.style = `
      -webkit-user-select: none;
      margin:0 auto;
      transition: background-color 300ms;
    `;

    // <input>
    const changePageInput = document.createElement('input');
    changePageInput.id = 'can-input';
    changePageInput.type = 'number';
    changePageInput.value = `${CURRENT_PAGE}`;
    changePageInput.disabled = true;
    changePageInput.style = `
      width: 45%;height: 80%;
      font-size:18px;text-align:center;
    `;

    // <label>
    const pageLabel = document.createElement('label');
    pageLabel.id = 'can-page';
    pageLabel.style =
      'width: 55%;height: 80%;font-size:18px;text-align:center;margin: 0;background-color: hsla(0, 0%, 90%, 90%);';
    pageLabel.textContent =
      PAGE_LOADED === bzData.page ? `${PAGE_LOADED}` : `${PAGE_LOADED}`;

    // <button>
    const runBtn = document.createElement('button');
    const previousBtn = document.createElement('button');
    const nextBtn = document.createElement('button');
    const downloadBtn = document.createElement('button');
    const scaleUpBtn = document.createElement('button');
    const scaleResetBtn = document.createElement('button');
    const scaleDownBtn = document.createElement('button');
    runBtn.id = BtnID.runBtn;
    previousBtn.id = BtnID.previousBtn;
    nextBtn.id = BtnID.nextBtn;
    downloadBtn.id = BtnID.downloadBtn;
    scaleUpBtn.id = BtnID.scaleUpBtn;
    scaleResetBtn.id = BtnID.scaleResetBtn;
    scaleDownBtn.id = BtnID.scaleDownBtn;
    runBtn.textContent = BtnText.runBtn;
    previousBtn.textContent = BtnText.previousBtn;
    nextBtn.textContent = BtnText.nextBtn;
    downloadBtn.textContent = BtnText.downloadBtn;
    scaleUpBtn.textContent = BtnText.scaleUpBtn;
    scaleResetBtn.textContent = BtnText.scaleResetBtn;
    scaleDownBtn.textContent = BtnText.scaleDownBtn;

    runBtn.addEventListener('click', (evt) => {
      evt.stopPropagation();
      // 异步加载图片信息
      if (!IS_INIT) {
        IS_INIT = true;
        processImgAsync(bzDataIterator);
      }
      if (!IS_RUN) {
        IS_RUN = true;
        evt.target.textContent = `原页 ${bzData.page}`;
        // 生效按钮
        let btns = document
          .querySelector('#can-app')
          .querySelectorAll('button');
        for (const btn of btns) {
          btn.disabled = false;
        }
        let inputPage = document.querySelector('#can-input');
        inputPage.disabled = false;
        // 显示 新 <img>
        let _newImg = document.querySelector('#can-div-img');
        _newImg.style.display = 'block';
      } else {
        IS_RUN = false;
        evt.target.textContent = BtnText.runBtn;
        // 无效按钮
        let btns = document
          .querySelector('#can-app')
          .querySelectorAll('button');
        for (const btn of btns) {
          btn.disabled = btn.id !== 'run' ? true : false;
        }
        let inputPage = document.querySelector('#can-input');
        inputPage.disabled = true;
        // 隐藏新 <img>
        let _newImg = document.querySelector('#can-div-img');
        _newImg.style.display = 'none';
      }
    });

    previousBtn.addEventListener('click', (evt) => {
      evt.stopPropagation();
      let imgInfo =
        bzData.imgInfoList[
          CURRENT_PAGE > 0 ? --CURRENT_PAGE : (CURRENT_PAGE = MAX_BROWSE_PAGE)
        ];
      updateImgTag(imgInfo);
      let inputPage = document.querySelector('#can-input');
      let page_ = document.querySelector('#can-page');
      inputPage.value = CURRENT_PAGE;
      if (PAGE_LOADED !== bzData.page) {
        let percentage = Number.parseInt((PAGE_LOADED / bzData.page) * 100);
        if (percentage < 25) page_.textContent = `${PAGE_LOADED} ◕`;
        else if (percentage < 50) page_.textContent = `${PAGE_LOADED} ◑`;
        else if (percentage < 75) page_.textContent = `${PAGE_LOADED} ◔`;
        else if (percentage < 100) page_.textContent = `${PAGE_LOADED} ☯`;
      } else {
        page_.textContent = `${PAGE_LOADED}`;
      }
    });

    nextBtn.addEventListener('click', (evt) => {
      evt.stopPropagation();
      let imgInfo =
        bzData.imgInfoList[
          CURRENT_PAGE < PAGE_LOADED
            ? ++CURRENT_PAGE
            : (CURRENT_PAGE = PAGE_LOADED !== bzData.page ? CURRENT_PAGE : 0) // 完全加载完前不会 '溢出跳 0'
        ];
      updateImgTag(imgInfo);
      let inputPage = document.querySelector('#can-input');
      let page_ = document.querySelector('#can-page');
      inputPage.value = CURRENT_PAGE;
      if (PAGE_LOADED !== bzData.page) {
        let percentage = Number.parseInt((PAGE_LOADED / bzData.page) * 100);
        if (percentage < 25) page_.textContent = `${PAGE_LOADED} ◕`;
        else if (percentage < 50) page_.textContent = `${PAGE_LOADED} ◑`;
        else if (percentage < 75) page_.textContent = `${PAGE_LOADED} ◔`;
        else if (percentage < 100) page_.textContent = `${PAGE_LOADED} ☯`;
      } else {
        page_.textContent = `${PAGE_LOADED}`;
      }

      if (MAX_BROWSE_PAGE < CURRENT_PAGE) MAX_BROWSE_PAGE = CURRENT_PAGE;
    });

    downloadBtn.addEventListener('click', (evt) => {
      evt.stopPropagation();

      // let stringData = JSON.stringify(bzData, null, 2);
      // let blob = new Blob([], { type: 'text/plain;charset=utf-8' });
      // saveAs(blob, 'PicLInk.json');

      // 打包 zip
      if (!IS_DOWNLOADING) {
        IS_DOWNLOADING = true;
        UserCustemRange.page_loaded = PAGE_LOADED;
        if (UserCustemRange.page_loaded !== bzData.page) {
          let result = confirm(
            `当前${UserCustemRange.page_loaded}页,图片未加载完全,是否继续?🤨`
          );
          if (!result) {
            IS_DOWNLOADING = false;
            return;
          }
        }
        let result = prompt(
          '选择下载页面区间,请使用 [英文符号 - ] 隔开😇',
          `0-${UserCustemRange.page_loaded}`
        );
        if (result) {
          let rangeRegExp = result.match(/^(\d+)-(\d+)$/);
          if (IS_DEBUG) console.log(`rangeRegExp: ${rangeRegExp}`);
          if (rangeRegExp) {
            UserCustemRange.min = Number.parseInt(rangeRegExp[1]);
            UserCustemRange.max = Number.parseInt(rangeRegExp[2]);
            // 处理意外输入
            if (
              !rangeRegExp ||
              0 > UserCustemRange.min ||
              UserCustemRange.min > UserCustemRange.max ||
              UserCustemRange.max > UserCustemRange.page_loaded
            ) {
              alert('无效输入😥');
              IS_DOWNLOADING = false;
              return;
            }
          } else {
            alert('无效输入😥');
            IS_DOWNLOADING = false;
            return;
          }
          watchImgInfoAsync(bzData, UserCustemRange.min, UserCustemRange.max);
          getImageBase64Async(bzData, UserCustemRange.min, UserCustemRange.max);
        } else {
          IS_DOWNLOADING = false;
        }
      }
    });

    scaleUpBtn.addEventListener('click', (evt) => {
      evt.stopPropagation();
      SCALE += SCALE < 3 ? 0.1 : 0;
      let imgInfo = bzData.imgInfoList[CURRENT_PAGE];
      updateImgTag(imgInfo);
    });

    scaleResetBtn.addEventListener('click', (evt) => {
      evt.stopPropagation();
      IS_FREE_SCALE = !IS_FREE_SCALE;

      SCALE = 1;
      let imgInfo = bzData.imgInfoList[CURRENT_PAGE];
      updateImgTag(imgInfo);
    });

    scaleDownBtn.addEventListener('click', (evt) => {
      evt.stopPropagation();
      SCALE -= SCALE > 0.3 ? 0.1 : 0;
      let imgInfo = bzData.imgInfoList[CURRENT_PAGE];
      updateImgTag(imgInfo);
    });

    changePageInput.addEventListener('change', (evt) => {
      evt.stopPropagation();
      if (0 <= evt.target.value && evt.target.value <= bzData.page) {
        CURRENT_PAGE = evt.target.value;
        let imgInfo = bzData.imgInfoList[CURRENT_PAGE];
        updateImgTag(imgInfo);
      }
    });

    const app = document.createElement('div');
    app.id = 'can-app';
    app.style = `
      font-size:20px; color:HotPink;
      width:120px; height:200px; background-color:hsla(0, 0%, 90%, 50%);
      display:flex; flex-direction:column; justify-content:space-between;
      position:fixed; top:40%; z-index:1000002; transform:translateX(calc(-50% * var(--direction))) translateY(-50%);
    `;
    const div_tool = document.createElement('div');
    div_tool.style = `
      display:flex; flex-direction:column; justify-content:space-between;
      height:160px; background-color:hsla(0, 0%, 90%, 50%);
    `;
    const div_scale = document.createElement('div');
    div_scale.style = `
      display:flex; flex-direction:row; justify-content:space-between;
    `;
    const div_page = document.createElement('div');
    div_page.style = `
      align-items:center;
      display:flex; flex-direction:row; justify-content:space-between;
    `;

    app.appendChild(runBtn);

    div_tool.appendChild(previousBtn);
    div_tool.appendChild(nextBtn);

    div_scale.appendChild(scaleUpBtn);
    div_scale.appendChild(scaleResetBtn);
    div_scale.appendChild(scaleDownBtn);
    div_tool.appendChild(div_scale);

    div_page.appendChild(changePageInput);
    div_page.appendChild(pageLabel);
    div_tool.appendChild(div_page);

    div_tool.appendChild(downloadBtn);
    app.appendChild(div_tool);

    document.body.appendChild(app);

    // 包裹 <img> 并悬浮居中
    const div_img = document.createElement('div');
    div_img.id = 'can-div-img';

    // 粉色
    div_img.style = `
        display:none;
        position: fixed;overflow: auto;
        width: 80%;height: 100%;top: 0%;z-index: 1000001;
        left: 0;right: 0;margin:0 auto;text-align: center;
        background-color: hsla(338, 100%, 70%, 0.8);
      `;

    div_img.appendChild(newImg);
    document.body.appendChild(div_img);

    // loading 初始隐藏
    // document.body.insertAdjacentHTML('beforeend', LOADING_SVG);

    let btns = document.querySelector('#can-app').querySelectorAll('button');
    for (const btn of btns) {
      btn.style = 'font-size:20px; color:HotPink;';
      btn.disabled = btn.id !== 'run' ? true : false;
    }
  }

  // 更新 <img>
  function updateImgTag(imgInfo) {
    let div_img = document.querySelector('#can-div-img');
    let newImg_ = document.querySelector('#can-img');
    newImg_.src = imgInfo.imgUrl;
    newImg_.alt = imgInfo.imgAlt;
    if (imgInfo.imgType !== '.err') {
      // 使用 * 缩放应该在 <原数值> 上变化
      newImg_.width = imgInfo.width * SCALE;
      newImg_.height = imgInfo.height * SCALE;
    } else {
      newImg_.style.removeProperty('width');
      newImg_.style.removeProperty('height');
    }
  }

  initComponents(...initData());
})();