// ==UserScript==
// @name img_preview
// @namespace http://tampermonkey.net/
// @version 0.0.8
// @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: 'onImgPreview({ onContentSelector: \'.js_content\',selector: \'img.wxw-img\' })\n function onImgPreview(options = {}) {\n let defaultOptions = {}\n let { onContentSelector,selector = \'img\' } = Object({},defaultOptions,options)\n let scale = 1\n let offset = { left: 0,top: 0 }\n let origin = \'center\'\n let initialData = { offset: {},origin: \'center\',scale: 1 }\n let startPoint = { x: 0,y: 0 } // 记录初始触摸点位\n let isTouching = false // 标记是否正在移动\n let isMove = false // 正在移动中,与点击做区别\n let touches = new Map() // 触摸点数组\n let lastDistance = 0\n let lastScale = 1 // 记录下最后的缩放值\n let scaleOrigin = { x: 0,y: 0,}\n\n const { innerWidth: winWidth,innerHeight: winHeight } = window\n // let cloneEl = null\n let cloneEl = document.createElement(\'img\')\n let originalEl = null\n\n let imgDoms = [...document.querySelectorAll(selector)]\n let onContent = onContentSelector ? document.querySelector(onContentSelector) : window\n onContent.addEventListener(\'click\',function (e) {\n e.preventDefault()\n if (imgDoms.find(item => item === e.target)) {\n originalEl = e.target\n // cloneEl = originalEl.cloneNode(true)\n cloneEl.src = originalEl.src\n originalEl.style.opacity = 0\n openPreview()\n }\n })\n\n function createStyle() {\n\n const style = document.createElement(\'style\')\n style.innerHTML = `/* 图片预览 */\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: rgba(0, 0, 0, 0.75);\n user-select: none;\n }\n\n .modal>img {\n position: absolute;\n padding: 0;\n margin: 0;\n /* transition: all var(--delay_time); */\n transform: translateZ(0);\n }`\n return style\n }\n function openPreview() {\n scale = 1\n const { offsetWidth,offsetHeight } = originalEl\n const { top,left } = originalEl.getBoundingClientRect()\n // 创建蒙层\n const mask = document.createElement(\'div\')\n mask.classList.add(\'modal\')\n mask.appendChild(createStyle())\n // 添加在body下\n document.body.appendChild(mask)\n // 注册(不可用)事件\n mask.addEventListener("click",clickFunc)\n mask.addEventListener(\'mousewheel\',zoom,{ passive: false })\n // 遮罩点击事件\n function clickFunc() {\n setTimeout(() => {\n if (isMove) {\n isMove = false\n } else {\n changeStyle(cloneEl,[\'transition: all .3s\',`left: ${left}px`,`top: ${top}px`,`transform: translate(0,0)`,`width: ${offsetWidth}px`])\n setTimeout(() => {\n document.body.removeChild(this)\n originalEl.style.opacity = 1\n mask.removeEventListener(\'click\',clickFunc)\n },300)\n }\n },280)\n }\n // 添加图片\n changeStyle(cloneEl,[`left: ${left}px`,`top: ${top}px`])\n mask.appendChild(cloneEl)\n // 移动图片到屏幕中心位置\n const originalCenterPoint = { x: offsetWidth / 2 + left,y: offsetHeight / 2 + top }\n const winCenterPoint = { x: winWidth / 2,y: winHeight / 2 }\n const offsetDistance = { left: winCenterPoint.x - originalCenterPoint.x + left,top: winCenterPoint.y - originalCenterPoint.y + top }\n const diffs = { left: ((adaptScale() - 1) * offsetWidth) / 2,top: ((adaptScale() - 1) * offsetHeight) / 2 }\n changeStyle(cloneEl,[\'transition: all 0.3s\',`width: ${offsetWidth * adaptScale() + \'px\'}`,`transform: translate(${offsetDistance.left - left - diffs.left}px, ${offsetDistance.top - top - diffs.top}px)`])\n // 消除偏差\n setTimeout(() => {\n changeStyle(cloneEl,[\'transition: all 0s\',`left: 0`,`top: 0`,`transform: translate(${offsetDistance.left - diffs.left}px, ${offsetDistance.top - diffs.top}px)`])\n offset = { left: offsetDistance.left - diffs.left,top: offsetDistance.top - diffs.top } // 记录值\n record()\n },300)\n }\n\n // 滚轮缩放\n const zoom = (event) => {\n if (!event.deltaY) {\n return\n }\n event.preventDefault()\n origin = `${event.offsetX}px ${event.offsetY}px`\n // 缩放执行\n if (event.deltaY < 0) {\n scale += 0.1 // 放大\n } else if (event.deltaY > 0) {\n scale >= 0.2 && (scale -= 0.1) // 缩小\n }\n if (scale < initialData.scale) {\n reduction()\n }\n offset = getOffsetCorrection(event.offsetX,event.offsetY)\n changeStyle(cloneEl,[\'transition: all .15s\',`transform-origin: ${origin}`,`transform: translate(${offset.left + \'px\'}, ${offset.top + \'px\'}) scale(${scale})`])\n }\n\n // 获取中心改变的偏差\n function getOffsetCorrection(x = 0,y = 0) {\n const touchArr = Array.from(touches)\n if (touchArr.length === 2) {\n const start = touchArr[0][1]\n const end = touchArr[1][1]\n x = (start.offsetX + end.offsetX) / 2\n y = (start.offsetY + end.offsetY) / 2\n }\n origin = `${x}px ${y}px`\n const offsetLeft = (scale - 1) * (x - scaleOrigin.x) + offset.left\n const offsetTop = (scale - 1) * (y - scaleOrigin.y) + offset.top\n scaleOrigin = { x,y }\n return { left: offsetLeft,top: offsetTop }\n }\n\n // 操作事件\n window.addEventListener(\'pointerdown\',function (e) {\n e.preventDefault()\n touches.set(e.pointerId,e) // TODO: 点击存入触摸点\n isTouching = true\n startPoint = { x: e.clientX,y: e.clientY }\n if (touches.size === 2) { // TODO: 判断双指触摸,并立即记录初始数据\n lastDistance = getDistance()\n lastScale = scale\n }\n })\n window.addEventListener(\'pointerup\',function (e) {\n touches.delete(e.pointerId) // TODO: 抬起移除触摸点\n if (touches.size <= 0) {\n isTouching = false\n } else {\n const touchArr = Array.from(touches)\n // 更新点位\n startPoint = { x: touchArr[0][1].clientX,y: touchArr[0][1].clientY }\n }\n setTimeout(() => {\n isMove = false\n },300);\n })\n window.addEventListener(\'pointermove\',(e) => {\n e.preventDefault()\n if (isTouching) {\n isMove = true\n if (touches.size < 2) { // 单指滑动\n offset = {\n left: offset.left + (e.clientX - startPoint.x),\n top: offset.top + (e.clientY - startPoint.y),\n }\n changeStyle(cloneEl,[\'transition: all 0s\',`transform: translate(${offset.left + \'px\'}, ${offset.top + \'px\'}) scale(${scale})`,`transform-origin: ${origin}`])\n // 更新点位\n startPoint = { x: e.clientX,y: e.clientY }\n } else {\n // 双指缩放\n touches.set(e.pointerId,e)\n const ratio = getDistance() / lastDistance\n scale = ratio * lastScale\n offset = getOffsetCorrection()\n if (scale < initialData.scale) {\n reduction()\n }\n changeStyle(cloneEl,[\'transition: all 0s\',`transform: translate(${offset.left + \'px\'}, ${offset.top + \'px\'}) scale(${scale})`,`transform-origin: ${origin}`])\n }\n }\n })\n window.addEventListener(\'pointercancel\',function (e) {\n touches.clear() // 可能存在特定事件导致中断,真机操作时 pointerup 在某些边界情况下不会生效,所以需要清空\n })\n\n // 修改样式,减少回流重绘\n function changeStyle(el,arr) {\n const original = el.style.cssText.split(\';\')\n original.pop()\n el.style.cssText = original.concat(arr).join(\';\') + \';\'\n }\n\n // 计算自适应屏幕的缩放值\n function adaptScale() {\n const { offsetWidth: w,offsetHeight: h } = originalEl\n let scale = 0\n scale = winWidth / w\n if (h * scale > winHeight - 80) {\n scale = (winHeight - 80) / h\n }\n return scale\n }\n\n // 获取距离\n function getDistance() {\n const touchArr = Array.from(touches)\n if (touchArr.length < 2) {\n return 0\n }\n const start = touchArr[0][1]\n const end = touchArr[1][1]\n return Math.hypot(end.x - start.x,end.y - start.y)\n }\n\n // 记录初始化数据\n function record() {\n initialData = Object.assign({},{ offset,origin,scale })\n }\n\n // 还原记录,用于边界处理\n let timer = null\n function reduction() {\n timer && clearTimeout(timer)\n timer = setTimeout(() => {\n // offset = initialData.offset\n // origin = initialData.origin\n // scale = initialData.scale\n changeStyle(cloneEl,[`transform: translate(${offset.left + \'px\'}, ${offset.top + \'px\'}) scale(${scale})`,`transform-origin: ${origin}`])\n },300)\n }\n }'
})
return
onImgPreview({ onContentSelector: '.js_content',selector: 'img.wxw-img' })
function onImgPreview(options = {}) {
let defaultOptions = {}
let { onContentSelector,selector = 'img' } = Object({},defaultOptions,options)
let scale = 1
let offset = { left: 0,top: 0 }
let origin = 'center'
let initialData = { offset: {},origin: 'center',scale: 1 }
let startPoint = { x: 0,y: 0 } // 记录初始触摸点位
let isTouching = false // 标记是否正在移动
let isMove = false // 正在移动中,与点击做区别
let touches = new Map() // 触摸点数组
let lastDistance = 0
let lastScale = 1 // 记录下最后的缩放值
let scaleOrigin = { x: 0,y: 0,}
const { innerWidth: winWidth,innerHeight: winHeight } = window
// let cloneEl = null
let cloneEl = document.createElement('img')
let originalEl = null
let imgDoms = [...document.querySelectorAll(selector)]
let onContent = onContentSelector ? document.querySelector(onContentSelector) : window
onContent.addEventListener('click',function (e) {
e.preventDefault()
if (imgDoms.find(item => item === e.target)) {
originalEl = e.target
// cloneEl = originalEl.cloneNode(true)
cloneEl.src = originalEl.src
originalEl.style.opacity = 0
openPreview()
}
})
function createStyle() {
const style = document.createElement('style')
style.innerHTML = `/* 图片预览 */
.modal {
touch-action: none;
position: fixed;
z-index: 99;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.75);
user-select: none;
}
.modal>img {
position: absolute;
padding: 0;
margin: 0;
/* transition: all var(--delay_time); */
transform: translateZ(0);
}`
return style
}
function openPreview() {
scale = 1
const { offsetWidth,offsetHeight } = originalEl
const { top,left } = originalEl.getBoundingClientRect()
// 创建蒙层
const mask = document.createElement('div')
mask.classList.add('modal')
mask.appendChild(createStyle())
// 添加在body下
document.body.appendChild(mask)
// 注册(不可用)事件
mask.addEventListener("click",clickFunc)
mask.addEventListener('mousewheel',zoom,{ passive: false })
// 遮罩点击事件
function clickFunc() {
setTimeout(() => {
if (isMove) {
isMove = false
} else {
changeStyle(cloneEl,['transition: all .3s',`left: ${left}px`,`top: ${top}px`,`transform: translate(0,0)`,`width: ${offsetWidth}px`])
setTimeout(() => {
document.body.removeChild(this)
originalEl.style.opacity = 1
mask.removeEventListener('click',clickFunc)
},300)
}
},280)
}
// 添加图片
changeStyle(cloneEl,[`left: ${left}px`,`top: ${top}px`])
mask.appendChild(cloneEl)
// 移动图片到屏幕中心位置
const originalCenterPoint = { x: offsetWidth / 2 + left,y: offsetHeight / 2 + top }
const winCenterPoint = { x: winWidth / 2,y: winHeight / 2 }
const offsetDistance = { left: winCenterPoint.x - originalCenterPoint.x + left,top: winCenterPoint.y - originalCenterPoint.y + top }
const diffs = { left: ((adaptScale() - 1) * offsetWidth) / 2,top: ((adaptScale() - 1) * offsetHeight) / 2 }
changeStyle(cloneEl,['transition: all 0.3s',`width: ${offsetWidth * adaptScale() + 'px'}`,`transform: translate(${offsetDistance.left - left - diffs.left}px, ${offsetDistance.top - top - diffs.top}px)`])
// 消除偏差
setTimeout(() => {
changeStyle(cloneEl,['transition: all 0s',`left: 0`,`top: 0`,`transform: translate(${offsetDistance.left - diffs.left}px, ${offsetDistance.top - diffs.top}px)`])
offset = { left: offsetDistance.left - diffs.left,top: offsetDistance.top - diffs.top } // 记录值
record()
},300)
}
// 滚轮缩放
const zoom = (event) => {
if (!event.deltaY) {
return
}
event.preventDefault()
origin = `${event.offsetX}px ${event.offsetY}px`
// 缩放执行
if (event.deltaY < 0) {
scale += 0.1 // 放大
} else if (event.deltaY > 0) {
scale >= 0.2 && (scale -= 0.1) // 缩小
}
if (scale < initialData.scale) {
reduction()
}
offset = getOffsetCorrection(event.offsetX,event.offsetY)
changeStyle(cloneEl,['transition: all .15s',`transform-origin: ${origin}`,`transform: translate(${offset.left + 'px'}, ${offset.top + 'px'}) scale(${scale})`])
}
// 获取中心改变的偏差
function getOffsetCorrection(x = 0,y = 0) {
const touchArr = Array.from(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
}
origin = `${x}px ${y}px`
const offsetLeft = (scale - 1) * (x - scaleOrigin.x) + offset.left
const offsetTop = (scale - 1) * (y - scaleOrigin.y) + offset.top
scaleOrigin = { x,y }
return { left: offsetLeft,top: offsetTop }
}
// 操作事件
window.addEventListener('pointerdown',function (e) {
e.preventDefault()
touches.set(e.pointerId,e) // TODO: 点击存入触摸点
isTouching = true
startPoint = { x: e.clientX,y: e.clientY }
if (touches.size === 2) { // TODO: 判断双指触摸,并立即记录初始数据
lastDistance = getDistance()
lastScale = scale
}
})
window.addEventListener('pointerup',function (e) {
touches.delete(e.pointerId) // TODO: 抬起移除触摸点
if (touches.size <= 0) {
isTouching = false
} else {
const touchArr = Array.from(touches)
// 更新点位
startPoint = { x: touchArr[0][1].clientX,y: touchArr[0][1].clientY }
}
setTimeout(() => {
isMove = false
},300);
})
window.addEventListener('pointermove',(e) => {
e.preventDefault()
if (isTouching) {
isMove = true
if (touches.size < 2) { // 单指滑动
offset = {
left: offset.left + (e.clientX - startPoint.x),
top: offset.top + (e.clientY - startPoint.y),
}
changeStyle(cloneEl,['transition: all 0s',`transform: translate(${offset.left + 'px'}, ${offset.top + 'px'}) scale(${scale})`,`transform-origin: ${origin}`])
// 更新点位
startPoint = { x: e.clientX,y: e.clientY }
} else {
// 双指缩放
touches.set(e.pointerId,e)
const ratio = getDistance() / lastDistance
scale = ratio * lastScale
offset = getOffsetCorrection()
if (scale < initialData.scale) {
reduction()
}
changeStyle(cloneEl,['transition: all 0s',`transform: translate(${offset.left + 'px'}, ${offset.top + 'px'}) scale(${scale})`,`transform-origin: ${origin}`])
}
}
})
window.addEventListener('pointercancel',function (e) {
touches.clear() // 可能存在特定事件导致中断,真机操作时 pointerup 在某些边界情况下不会生效,所以需要清空
})
// 修改样式,减少回流重绘
function changeStyle(el,arr) {
const original = el.style.cssText.split(';')
original.pop()
el.style.cssText = original.concat(arr).join(';') + ';'
}
// 计算自适应屏幕的缩放值
function adaptScale() {
const { offsetWidth: w,offsetHeight: h } = originalEl
let scale = 0
scale = winWidth / w
if (h * scale > winHeight - 80) {
scale = (winHeight - 80) / h
}
return scale
}
// 获取距离
function getDistance() {
const touchArr = Array.from(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)
}
// 记录初始化数据
function record() {
initialData = Object.assign({},{ offset,origin,scale })
}
// 还原记录,用于边界处理
let timer = null
function reduction() {
timer && clearTimeout(timer)
timer = setTimeout(() => {
// offset = initialData.offset
// origin = initialData.origin
// scale = initialData.scale
changeStyle(cloneEl,[`transform: translate(${offset.left + 'px'}, ${offset.top + 'px'}) scale(${scale})`,`transform-origin: ${origin}`])
},300)
}
}
})();