// ==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)"
});