边读边看图

图片预览:通过双击按键,把图片固定在页面上,边读文字边看图,同时支持缩放、移动功能

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

// ==UserScript==
// @name         边读边看图
// @namespace    http://tampermonkey.net/
// @version      0.3.0
// @description  图片预览:通过双击按键,把图片固定在页面上,边读文字边看图,同时支持缩放、移动功能
// @author       Enjoy
// @icon         https://foruda.gitee.com/avatar/1671100286067517749/4867929_enjoy_li_1671100285.png!avatar60
// @match        *://*/*
// @exclude    *hrwork*
// @exclude    *zhaopinyun*
// @exclude    *localhost*
// @exclude    *127.0.0.1*
// @grant        GM_addElement
// @grant        GM_setClipboard
// @license      GPL License
// ==/UserScript==

// 函数文档 https://www.tampermonkey.net/documentation.php#api:GM_addElement
// @match        *://mp.weixin.qq.com/s/*
"use strict";

function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
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); }
GM_addElement('style', {
  textContent: ".pages_skin_pc .rich_media_area_primary_inner{margin-left:initial;}",
  id: 'img_preview_style'
});
var ImgPreviwer = /*#__PURE__*/function () {
  function ImgPreviwer() {
    var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
    _classCallCheck(this, ImgPreviwer);
    /** @描述 状态 */
    _defineProperty(this, "state", null);
    _defineProperty(this, "shadowRoot", null);
    this.state = this.mergeOptions(options);
    this.shadowRoot = this.createShadowRoot();
    this.onPreviwerEvent();
    return this.shadowRoot;
  }
  _createClass(ImgPreviwer, [{
    key: "createShadowRoot",
    value: /** @描述 创建 shadowRoot */
    function createShadowRoot() {
      var selector = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '#imgPreview';
      var dom = document.querySelector("".concat(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) {
        // 创建蒙层容器
        var 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;
    }
    /** @描述 合并选项 */
  }, {
    key: "mergeOptions",
    value: function mergeOptions(options) {
      var opt = {};
      var defaultOptions = {
        contentSelector: 'body',
        selector: 'img',
        showRootSelector: '#img_preview',
        backgroundColor: "rgba(0,0,0,0)",
        extraStyle: ''
      };
      Object.assign(opt, defaultOptions, options);
      return opt;
    }
    /** @描述 创建shadowbox中的样式 */
  }, {
    key: "createStyle",
    value: function createStyle(_ref) {
      var contentSelector = _ref.contentSelector,
        selector = _ref.selector,
        backgroundColor = _ref.backgroundColor,
        extraStyle = _ref.extraStyle;
      var style = document.createElement('style');
      style.innerHTML = "".concat(contentSelector, " ").concat(selector, " {\n                                cursor: zoom-in;\n                              }\n                                /* \u56FE\u7247\u9884\u89C8 */\n                                .modal {\n                                touch-action: none;\n                                position: fixed;\n                                z-index: 99;\n                                top: 0;\n                                left: 0;\n                                width: 100vw;\n                                height: 100vh;\n                                background-color: ").concat(backgroundColor, ";\n                                user-select: none;\n                                pointer-events: none;\n                                }\n                                .modal>*{\n                                  pointer-events: auto;\n                                }\n                                .modal>img {\n                                position: absolute;\n                                padding: 0;\n                                margin: 0;\n                                box-shadow: #09818f 0px 0px 15px 0px;\n                                border-radius: 10px;\n                                /* transition: all var(--delay_time); */\n                                transform: translateZ(0);\n                                }\n\n                                img.active {\n                                  animation: activeImg 0.5s 4 ease-out forwards;\n                                  transition: all;\n                                }\n\n                                @keyframes activeImg {\n                                  0% {\n                                    box-shadow: #09818f 0px 0px 15px 0px;\n                                  }\n                                  50% {\n                                    box-shadow: red 0px 0px 50px 0px;\n                                  }\n                                  100% {\n                                    box-shadow: #09818f 0px 0px 15px 0px;\n                                  }\n                                }\n                                ").concat(extraStyle, "\n                                ");
      return style;
    }
    /** @描述 预览操作 */
  }, {
    key: "onPreviwerEvent",
    value: function onPreviwerEvent() {
      var that = this;
      var _that$state = that.state,
        contentSelector = _that$state.contentSelector,
        selector = _that$state.selector;
      var eventsProxy = document.querySelector(contentSelector) || window.document.body;
      eventsProxy.addEventListener('dblclick', function (e) {
        var _window$getComputedSt;
        var 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();
        var findOneInPage = _toConsumableArray(eventsProxy.querySelectorAll(selector)).find(function (item) {
          return item === e.target;
        });
        if (findOneInPage) {
          var findOneInModal = _toConsumableArray(that.shadowRoot.querySelectorAll(selector)).find(function (item) {
            return 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);
          }
        }
      });
    }
  }]);
  return ImgPreviwer;
}();
var ImgPreviewer = /*#__PURE__*/function () {
  function ImgPreviewer(shadowRoot, originalEl, src) {
    var _this = this;
    _classCallCheck(this, ImgPreviewer);
    _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", function (e) {
      e.preventDefault();
      var that = _this;
      var state = that.state;
      setTimeout(function () {
        if (state.isMove) {
          state.isMove = false;
        } else {
          that.changeStyle(state.cloneEl, ['transition: all .3s', "left: ".concat(state.left, "px"), "top: ".concat(state.top, "px"), "transform: translate(0,0)", "width: ".concat(state.offsetWidth, "px")]);
          setTimeout(function () {
            state.maskContent.removeChild(state.cloneEl);
            // originalEl.style.opacity = 1
            state.cloneEl.removeEventListener('dblclick', that.ondblclick);
          }, 300);
        }
      }, 280);
    });
    /** @描述  指针按下事件*/
    _defineProperty(this, "onpointerdown", function (e) {
      e.preventDefault();
      var that = _this;
      var 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", function (e) {
      e.preventDefault();
      if (!e.deltaY) return;
      var that = _this;
      var state = that.state;
      state.origin = "".concat(e.offsetX, "px ").concat(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: ".concat(state.origin), "transform: translate(".concat(state.offset.left + 'px', ", ").concat(state.offset.top + 'px', ") scale(").concat(state.scale, ")")]);
    });
    /** @描述 松开指针 事件 */
    _defineProperty(this, "onpointerup", function (e) {
      e.preventDefault();
      var that = _this;
      var state = that.state;
      state.touches["delete"](e.pointerId);
      // TODO: 抬起移除触摸点
      if (state.touches.size <= 0) {
        state.isTouching = false;
      } else {
        var touchArr = Array.from(state.touches);
        // 更新点位
        state.startPoint = {
          x: touchArr[0][1].clientX,
          y: touchArr[0][1].clientY
        };
      }
      setTimeout(function () {
        state.isMove = false;
      }, 300);
    });
    /** @描述 指针移动事件 */
    _defineProperty(this, "onpointermove", function (e) {
      e.preventDefault();
      var that = _this;
      var 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(".concat(state.offset.left + 'px', ", ").concat(state.offset.top + 'px', ") scale(").concat(state.scale, ")"), "transform-origin: ".concat(origin)]);
          // 更新点位
          state.startPoint = {
            x: e.clientX,
            y: e.clientY
          };
        } else {
          // 双指缩放
          state.touches.set(e.pointerId, e);
          var 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(".concat(state.offset.left + 'px', ", ").concat(state.offset.top + 'px', ") scale(").concat(state.scale, ")"), "transform-origin: ".concat(state.origin)]);
        }
      }
    });
    /** @描述 取消指针事件 */
    _defineProperty(this, "onpointercancel", function (e) {
      e.preventDefault();
      _this.state.touches.clear();
      // 可能存在特定事件导致中断,真机操作时 pointerup 在某些边界情况下不会生效,所以需要清空
    });
    this.state = Object.assign({}, this.state, this.mergeOptions(shadowRoot, originalEl, src));
    var cloneEl = this.appendImg(src);
    this.state.cloneEl = cloneEl;
    this.fixPosition(cloneEl);
    this.addEvents(cloneEl);
    return cloneEl;
  }
  _createClass(ImgPreviewer, [{
    key: "mergeOptions",
    value: function mergeOptions(shadowRoot, originalEl, src) {
      var _window = window,
        winWidth = _window.innerWidth,
        winHeight = _window.innerHeight;
      var offsetWidth = originalEl.offsetWidth,
        offsetHeight = originalEl.offsetHeight;

      // Element.getBoundingClientRect() 方法返回元素的大小及其相对于【视口】的位置
      var _originalEl$getBoundi = originalEl.getBoundingClientRect(),
        top = _originalEl$getBoundi.top,
        left = _originalEl$getBoundi.left;
      return {
        shadowRoot: shadowRoot,
        originalEl: originalEl,
        src: src,
        winWidth: winWidth,
        winHeight: winHeight,
        offsetWidth: offsetWidth,
        offsetHeight: offsetHeight,
        top: top,
        left: left,
        maskContent: shadowRoot.querySelector('.modal')
      };
    }
    /** @描述 添加图片 */
  }, {
    key: "appendImg",
    value: function appendImg(src) {
      var cloneEl = document.createElement('img');
      cloneEl.src = src;
      this.state.maskContent.appendChild(cloneEl);
      return cloneEl;
    }
    /** @描述 添加监听事件 */
  }, {
    key: "addEvents",
    value: function addEvents(cloneEl) {
      var events = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ['dblclick', 'mousewheel', 'pointerdown', 'pointerup', 'pointermove', 'pointercancel'];
      var that = this;
      events.forEach(function (item) {
        if (item === 'mousewheel') {
          cloneEl.addEventListener('mousewheel', that["on".concat(item)], {
            passive: false
          });
          return;
        }
        cloneEl.addEventListener(item, that["on".concat(item)]);
      });
    }
  }, {
    key: "getOffsetPageCenter",
    value: /** @描述 获取中心改变的偏差 */
    function getOffsetPageCenter() {
      var x = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
      var y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
      var state = this.state;
      var touchArr = Array.from(state.touches);
      if (touchArr.length === 2) {
        var start = touchArr[0][1];
        var end = touchArr[1][1];
        x = (start.offsetX + end.offsetX) / 2;
        y = (start.offsetY + end.offsetY) / 2;
      }
      state.origin = "".concat(x, "px ").concat(y, "px");
      var offsetLeft = (state.scale - 1) * (x - state.scaleOrigin.x) + state.offset.left;
      var offsetTop = (state.scale - 1) * (y - state.scaleOrigin.y) + state.offset.top;
      state.scaleOrigin = {
        x: x,
        y: y
      };
      return {
        left: offsetLeft,
        top: offsetTop
      };
    }

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

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

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

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

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

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

      /** @描述 放大后的 */
      var scaleNum = this.adaptScale();
      var diffs = {
        left: (scaleNum - 1) * state.offsetWidth / 2,
        top: (scaleNum - 1) * state.offsetHeight / 2
      };
      console.log("state => %O ", state);
      this.changeStyle(cloneEl, ["left: ".concat(state.left, "px"), "top: ".concat(state.top, "px"), 'transition: all 0.3s', "width: ".concat(state.offsetWidth * scaleNum + 'px'), "transform: translate(".concat(offsetDistance.left - state.left - diffs.left, "px, ").concat(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)`])
      //   that.state.offset = {
      //     left: offsetDistance.left - diffs.left,
      //     top: offsetDistance.top - diffs.top
      //   }
      //   // 记录值
      //   that.record()
      // },300)
    }
    /** @描述 记录初始化数据 */
  }, {
    key: "record",
    value: function record() {
      var state = this.state;
      state.initialData = Object.assign({}, {
        offset: state.offset,
        origin: state.origin,
        scale: state.scale
      });
    }
    /** @描述 计算自适应屏幕的缩放 */
  }, {
    key: "adaptScale",
    value: function adaptScale() {
      var _this$state = this.state,
        winWidth = _this$state.winWidth,
        winHeight = _this$state.winHeight,
        originalEl = _this$state.originalEl;
      var w = originalEl.offsetWidth,
        h = originalEl.offsetHeight;
      var scale = winWidth / w;
      if (h * scale > winHeight - 80) {
        scale = (winHeight - 80) / h;
      }
      return scale;
    }
  }]);
  return ImgPreviewer;
}();
var shadowRoot = new ImgPreviwer({
  backgroundColor: "rgba(0,0,0,0)"
});

QingJ © 2025

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