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