img_preview

微信公证号文章中,图片预览

目前為 2023-08-01 提交的版本,檢視 最新版本

// ==UserScript==
// @name         img_preview
// @namespace    http://tampermonkey.net/
// @version      0.0.19
// @description  微信公证号文章中,图片预览
// @author       Enjoy
// @icon         https://foruda.gitee.com/avatar/1671100286067517749/4867929_enjoy_li_1671100285.png!avatar60
// @match        *://mp.weixin.qq.com/s/*
// @grant        GM_addElement
// @grant        GM_addStyle
// @grant        GM_setClipboard
// @license      GPL License
// ==/UserScript==

// 函数文档 https://www.tampermonkey.net/documentation.php#api:GM_addElement

(function () {
  GM_addElement('script',{
    textContent: `strFn();${strFn.toString()}`
  })
  return
  function strFn() {
  "use strict";

function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
class ImgPreviwer {
  constructor(options = {}) {
    /** @描述 状态 */
    _defineProperty(this, "state", null);
    _defineProperty(this, "shadowRoot", null);
    this.state = this.mergeOptions(options);
    this.shadowRoot = this.createShadowRoot();
    this.onPreviwerEvent();
    return this.shadowRoot;
  }
  /** @描述 创建 shadowRoot */
  createShadowRoot(selector = '#imgPreview') {
    let dom = document.querySelector(`${selector}`);
    if (!dom) {
      dom = document.createElement('div');
      dom.setAttribute('id', selector.replace(/[.#]/g, ''));
      dom.setAttribute('style', 'width:0;height:0');
      document.documentElement.appendChild(dom);
    }
    if (!dom.shadowRoot) {
      // 创建蒙层容器
      const maskContent = document.createElement('div');
      maskContent.classList.add('modal');
      maskContent.appendChild(this.createStyle(this.state));
      // 添加在body下
      dom.attachShadow({
        mode: 'open'
      });
      dom.shadowRoot.appendChild(maskContent);
    }
    return dom.shadowRoot;
  }
  /** @描述 合并选项 */
  mergeOptions(options) {
    let opt = {};
    let defaultOptions = {
      contentSelector: 'body',
      selector: 'img',
      showRootSelector: '#img_preview',
      backgroundColor: "rgba(0,0,0,0)"
    };
    Object.assign(opt, defaultOptions, options);
    return opt;
  }
  /** @描述 创建shadowbox中的样式 */
  createStyle({contentSelector,selector,backgroundColor,extraStyle}) {
    const style = document.createElement('style');
    style.innerHTML = `
                          ${contentSelector} ${selector} {
                            cursor: zoom-in;
                          }
                            /* 图片预览 */
                            .modal {
                            touch-action: none;
                            position: fixed;
                            z-index: 99;
                            top: 0;
                            left: 0;
                            width: 100vw;
                            height: 100vh;
                            background-color: ${backgroundColor};
                            user-select: none;
                            pointer-events: none;
                            }
                            .modal>*{
                              pointer-events: auto;
                            }
                            .modal>img {
                            position: absolute;
                            padding: 0;
                            margin: 0;
                            box-shadow: #09818f 0px 0px 15px 0px;
                            border-radius: 10px;
                            /* transition: all var(--delay_time); */
                            transform: translateZ(0);
                            }

                            img.active {
                              animation: activeImg 0.5s 4 ease-out forwards;
                              transition: all;
                            }

                            @keyframes activeImg {
                              0% {
                                box-shadow: #09818f 0px 0px 15px 0px;
                              }
                              50% {
                                box-shadow: red 0px 0px 100px 0px;
                              }
                              100% {
                                box-shadow: #09818f 0px 0px 15px 0px;
                              }
                            }
                            ${extraStyle}
                            `;
    return style;
  }
  /** @描述 预览操作 */
  onPreviwerEvent() {
    let that = this;
    let {
      contentSelector,
      selector
    } = that.state;
    let eventsProxy = document.querySelector(contentSelector) || window.document.body;
    eventsProxy.addEventListener('dblclick', function (e) {
      var _window$getComputedSt;
      let src = e.target.src || ((_window$getComputedSt = window.getComputedStyle(e.target).backgroundImage.match(/^url\("([^\s]+)"\)$/i)) === null || _window$getComputedSt === void 0 ? void 0 : _window$getComputedSt[1]);
      if (!src) return;
      e.preventDefault();
      let findOneInPage = [...eventsProxy.querySelectorAll(selector)].find(item => item === e.target);
      if (findOneInPage) {
        let findOneInModal = [...that.shadowRoot.querySelectorAll(selector)].find(item => item.src === src);
        if (findOneInModal) {
          if (!findOneInModal.classList.contains('active')) {
            findOneInModal.classList.add('active');
            return;
          } else {
            findOneInModal.remove();
            findOneInModal = null;
          }
        }
        if (!findOneInModal) {
          // originalEl.style.opacity = 0
          new ImgPreviewer(that.shadowRoot, e.target, src);
        }
      }
    });
  }
}
class ImgPreviewer {
  constructor(shadowRoot, originalEl, src) {
    _defineProperty(this, "state", {
      scale: 1,
      offset: {
        left: 0,
        top: 0
      },
      origin: 'center',
      initialData: {
        offset: {},
        origin: 'center',
        scale: 1
      },
      startPoint: {
        x: 0,
        y: 0
      },
      // 记录初始触摸点位
      isTouching: false,
      // 标记是否正在移动
      isMove: false,
      // 正在移动中,与点击做区别
      touches: new Map(),
      // 触摸点数组
      lastDistance: 0,
      lastScale: 1,
      // 记录下最后的缩放值
      scaleOrigin: {
        x: 0,
        y: 0
      }
    });
    /** @描述 双击事件 */
    _defineProperty(this, "ondblclick", e => {
      e.preventDefault();
      let that = this;
      let state = that.state;
      setTimeout(() => {
        if (state.isMove) {
          state.isMove = false;
        } else {
          that.changeStyle(state.cloneEl, ['transition: all .3s', `left: ${state.left}px`, `top: ${state.top}px`, `transform: translate(0,0)`, `width: ${state.offsetWidth}px`]);
          setTimeout(() => {
            state.maskContent.removeChild(state.cloneEl);
            // originalEl.style.opacity = 1
            state.cloneEl.removeEventListener('dblclick', that.ondblclick);
          }, 300);
        }
      }, 280);
    });
    /** @描述  指针按下事件*/
    _defineProperty(this, "onpointerdown", e => {
      e.preventDefault();
      let that = this;
      let state = that.state;
      state.touches.set(e.pointerId, e);
      // TODO: 点击存入触摸点
      state.isTouching = true;
      state.startPoint = {
        x: e.clientX,
        y: e.clientY
      };
      if (state.touches.size === 2) {
        // TODO: 判断双指触摸,并立即记录初始数据
        state.lastDistance = that.getDistance();
        state.lastScale = state.scale;
      }
    });
    /** @描述 滚轮缩放 */
    _defineProperty(this, "onmousewheel", e => {
      e.preventDefault();
      if (!e.deltaY) return;
      let that = this;
      let state = that.state;
      state.origin = `${e.offsetX}px ${e.offsetY}px`;

      // 缩放执行
      if (e.deltaY < 0) {
        // 放大
        state.scale += 0.1;
      } else if (e.deltaY > 0) {
        state.scale >= 0.2 && (state.scale -= 0.1);
        // 缩小
      }

      if (state.scale < state.initialData.scale) {
        console.log(`state.scale < state.initialData.scale => %O `, state.scale, state.initialData.scale);
        that.reduction();
      }
      state.offset = that.getOffsetPageCenter(e.offsetX, e.offsetY);
      that.changeStyle(state.cloneEl, ['transition: all .15s', `transform-origin: ${state.origin}`, `transform: translate(${state.offset.left + 'px'}, ${state.offset.top + 'px'}) scale(${state.scale})`]);
    });
    /** @描述 松开指针 事件 */
    _defineProperty(this, "onpointerup", e => {
      e.preventDefault();
      let that = this;
      let state = that.state;
      state.touches.delete(e.pointerId);
      // TODO: 抬起移除触摸点
      if (state.touches.size <= 0) {
        state.isTouching = false;
      } else {
        const touchArr = Array.from(state.touches);
        // 更新点位
        state.startPoint = {
          x: touchArr[0][1].clientX,
          y: touchArr[0][1].clientY
        };
      }
      setTimeout(() => {
        state.isMove = false;
      }, 300);
    });
    /** @描述 指针移动事件 */
    _defineProperty(this, "onpointermove", e => {
      e.preventDefault();
      let that = this;
      let state = that.state;
      if (state.isTouching) {
        state.isMove = true;
        if (state.touches.size < 2) {
          // 单指滑动
          state.offset = {
            left: state.offset.left + (e.clientX - state.startPoint.x),
            top: state.offset.top + (e.clientY - state.startPoint.y)
          };
          that.changeStyle(state.cloneEl, ['transition: all 0s', `transform: translate(${state.offset.left + 'px'}, ${state.offset.top + 'px'}) scale(${state.scale})`, `transform-origin: ${origin}`]);
          // 更新点位
          state.startPoint = {
            x: e.clientX,
            y: e.clientY
          };
        } else {
          // 双指缩放
          state.touches.set(e.pointerId, e);
          const ratio = that.getDistance() / state.lastDistance;
          state.scale = ratio * state.lastScale;
          state.offset = that.getOffsetPageCenter();
          if (state.scale < state.initialData.scale) {
            that.reduction();
          }
          that.changeStyle(state.cloneEl, ['transition: all 0s', `transform: translate(${state.offset.left + 'px'}, ${state.offset.top + 'px'}) scale(${state.scale})`, `transform-origin: ${state.origin}`]);
        }
      }
    });
    /** @描述 取消指针事件 */
    _defineProperty(this, "onpointercancel", e => {
      e.preventDefault();
      this.state.touches.clear();
      // 可能存在特定事件导致中断,真机操作时 pointerup 在某些边界情况下不会生效,所以需要清空
    });
    this.state = Object.assign({}, this.state, this.formateOptions(shadowRoot, originalEl, src));
    let cloneEl = this.appendImg(src);
    this.state.cloneEl = cloneEl;
    this.fixPosition(cloneEl);
    this.addEvents(cloneEl);
    return cloneEl;
  }
  formateOptions(shadowRoot, originalEl, src) {
    const {
      innerWidth: winWidth,
      innerHeight: winHeight
    } = window;
    const {
      offsetWidth,
      offsetHeight
    } = originalEl;
    const {
      top,
      left
    } = originalEl.getBoundingClientRect();
    return {
      shadowRoot,
      originalEl,
      src,
      winWidth,
      winHeight,
      offsetWidth,
      offsetHeight,
      top,
      left,
      maskContent: shadowRoot.querySelector('.modal')
    };
  }
  /** @描述 添加图片 */
  appendImg(src) {
    let cloneEl = document.createElement('img');
    cloneEl.src = src;
    this.state.maskContent.appendChild(cloneEl);
    return cloneEl;
  }
  /** @描述 添加监听事件 */
  addEvents(cloneEl, events = ['dblclick', 'mousewheel', 'pointerdown', 'pointerup', 'pointermove', 'pointercancel']) {
    let that = this;
    events.forEach(item => {
      if (item === 'mousewheel') {
        cloneEl.addEventListener('mousewheel', that[`on${item}`], {
          passive: false
        });
        return;
      }
      cloneEl.addEventListener(item, that[`on${item}`]);
    });
  }
  /** @描述 获取中心改变的偏差 */
  getOffsetPageCenter(x = 0, y = 0) {
    let state = this.state;
    const touchArr = Array.from(state.touches);
    if (touchArr.length === 2) {
      const start = touchArr[0][1];
      const end = touchArr[1][1];
      x = (start.offsetX + end.offsetX) / 2;
      y = (start.offsetY + end.offsetY) / 2;
    }
    state.origin = `${x}px ${y}px`;
    const offsetLeft = (state.scale - 1) * (x - state.scaleOrigin.x) + state.offset.left;
    const offsetTop = (state.scale - 1) * (y - state.scaleOrigin.y) + state.offset.top;
    state.scaleOrigin = {
      x,
      y
    };
    return {
      left: offsetLeft,
      top: offsetTop
    };
  }

  /** @描述  获取距离*/
  getDistance() {
    const touchArr = Array.from(this.state.touches);
    if (touchArr.length < 2) {
      return 0;
    }
    const start = touchArr[0][1];
    const end = touchArr[1][1];
    return Math.hypot(end.x - start.x, end.y - start.y);
  }

  /** @描述  修改样式,减少回流重绘*/
  changeStyle(el, arr) {
    const original = el.style.cssText.split(';');
    original.pop();
    el.style.cssText = original.concat(arr).join(';') + ';';
  }

  /** @描述 还原记录,用于边界处理 */
  reduction() {
    let that = this;
    let state = that.state;
    that.timer && clearTimeout(that.timer);
    that.timer = setTimeout(() => {
      // offset = state.initialData.offset
      // origin = state.initialData.origin
      // scale = state.initialData.scale
      console.log(`state => %O `, state);
      that.changeStyle(state.cloneEl, [`transform: translate(${state.offset.left + 'px'}, ${state.offset.top + 'px'}) scale(${state.scale})`, `transform-origin: ${state.origin}`]);
    }, 300);
  }
  /** @描述 移动图片到屏幕中心位置 */
  fixPosition(cloneEl) {
    let that = this;
    let state = that.state;

    /** @描述 原图片 中心点 */
    const originalCenterPoint = {
      x: state.offsetWidth / 2 + state.left,
      y: state.offsetHeight / 2 + state.top
    };

    /** @描述 页面 中心点 */
    const winCenterPoint = {
      x: state.winWidth / 2,
      y: state.winHeight / 2
    };

    /** @描述  新建图片的定位点:通过原图片中心点到页面中心点的 偏移量*/
    const offsetDistance = {
      left: winCenterPoint.x - originalCenterPoint.x + state.left,
      top: winCenterPoint.y - originalCenterPoint.y + state.top
    };

    /** @描述 放大后的 */
    const diffs = {
      left: (this.adaptScale(state.originalEl) - 1) * state.offsetWidth / 2,
      top: (this.adaptScale(state.originalEl) - 1) * state.offsetHeight / 2
    };
    this.changeStyle(cloneEl, [`left: ${state.left}px`, `top: ${state.top}px`, 'transition: all 0.3s', `width: ${state.offsetWidth * this.adaptScale(state.originalEl) + 'px'}`, `transform: translate(${offsetDistance.left - state.left - diffs.left}px, ${offsetDistance.top - state.top - diffs.top}px)`]);

    /** @描述 消除偏差:让图片相对于window  0 0定位,通过translate设置中心点重合*/
    setTimeout(() => {
      that.changeStyle(cloneEl, ['transition: all 0s', `left: 0`, `top: 0`, `transform: translate(${offsetDistance.left - diffs.left}px, ${offsetDistance.top - diffs.top}px)`]);
      console.log(`offsetDistance3 => %O `, offsetDistance, diffs);
      that.state.offset = {
        left: offsetDistance.left - diffs.left,
        top: offsetDistance.top - diffs.top
      };
      // 记录值
      that.record();
    }, 300);
  }
  /** @描述 记录初始化数据 */
  record() {
    let state = this.state;
    state.initialData = Object.assign({}, {
      offset: state.offset,
      origin: state.origin,
      scale: state.scale
    });
  }
  /** @描述 计算自适应屏幕的缩放 */
  adaptScale(originalEl) {
    let {
      winWidth,
      winHeight
    } = this.state;
    const {
      offsetWidth: w,
      offsetHeight: h
    } = originalEl;
    let scale = winWidth / w;
    if (h * scale > winHeight - 80) {
      scale = (winHeight - 80) / h;
    }
    return scale;
  }
}
let shadowRoot = new ImgPreviwer({
  backgroundColor: "rgba(0,0,0,0)"
});
}

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址