dA_zoomImage

better zoom for images in deviation-view

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         dA_zoomImage
// @namespace    http://phi.pf-control.de/userscripts/dA_zoomImage
// @version      2.0
// @description  better zoom for images in deviation-view
// @author       Dediggefedde
// @match        https://www.deviantart.com/*
// @grant        GM.setValue
// @grant        GM.getValue
// ==/UserScript==

/* jshint esnext:true */

(function() {
	'use strict';

	//%%% globale variables

	//imgGear copied from inkscape "render gear", slightly adjusted
	let imgGear = '<svg  xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 20.444057 20.232336" > <g transform="translate(-15.480352,-5.6695418)">  <g transform="matrix(0.26458333,0,0,0.26458333,25.702381,15.78571)"  style="fill:#000000">  <path  style="fill:#000000;stroke:#000000;stroke-width:1"  d="m 28.46196,-3.25861 4.23919,-0.48535 0.51123,0.00182 4.92206,1.5536 v 4.37708 l -4.92206,1.5536 -0.51123,0.00182 -4.23919,-0.48535 -1.40476,6.15466 4.02996,1.40204 0.45982,0.22345 3.76053,3.53535 -1.89914,3.94361 -5.1087,-0.73586 -0.4614,-0.22017 -3.60879,-2.2766 -3.93605,4.93565 3.02255,3.01173 0.31732,0.40083 1.8542,4.81687 -3.42214,2.72907 -4.2835,-2.87957 -0.32017,-0.39856 -2.26364,-3.61694 -5.68776,2.73908 1.41649,4.0249 0.11198,0.49883 -0.41938,5.14435 -4.26734,0.97399 -2.6099,-4.45294 -0.11554,-0.49801 -0.47013,-4.2409 h -6.31294 l -0.47013,4.2409 -0.11554,0.49801 -2.6099,4.45294 -4.26734,-0.97399 -0.41938,-5.14435 0.11198,-0.49883 1.41649,-4.0249 -5.68776,-2.73908 -2.26364,3.61694 -0.32017,0.39856 -4.2835,2.87957 -3.42214,-2.72907 1.8542,-4.81687 0.31732,-0.40083 3.02255,-3.01173 -3.93605,-4.93565 -3.60879,2.2766 -0.4614,0.22017 -5.1087,0.73586 -1.89914,-3.94361 3.76053,-3.53535 0.45982,-0.22345 4.02996,-1.40204 -1.40476,-6.15466 -4.23919,0.48535 -0.51123,-0.00182 -4.92206,-1.5536 v -4.37708 l 4.92206,-1.5536 0.51123,-0.00182 4.23919,0.48535 1.40476,-6.15466 -4.02996,-1.40204 -0.45982,-0.22345 -3.76053,-3.53535 1.89914,-3.94361 5.1087,0.73586 0.4614,0.22017 3.60879,2.2766 3.93605,-4.93565 -3.02255,-3.01173 -0.31732,-0.40083 -1.8542,-4.81687 3.42214,-2.72907 4.2835,2.87957 0.32017,0.39856 2.26364,3.61694 5.68776,-2.73908 -1.41649,-4.0249 -0.11198,-0.49883 0.41938,-5.14435 4.26734,-0.97399 2.6099,4.45294 0.11554,0.49801 0.47013,4.2409 h 6.31294 l 0.47013,-4.2409 0.11554,-0.49801 2.6099,-4.45294 4.26734,0.97399 0.41938,5.14435 -0.11198,0.49883 -1.41649,4.0249 5.68776,2.73908 2.26364,-3.61694 0.32017,-0.39856 4.2835,-2.87957 3.42214,2.72907 -1.8542,4.81687 -0.31732,0.40083 -3.02255,3.01173 3.93605,4.93565 3.60879,-2.2766 0.4614,-0.22017 5.1087,-0.73586 1.89914,3.94361 -3.76053,3.53535 -0.45982,0.22345 -4.02996,1.40204 z"  />  <circle  style="fill:#ffffff;stroke:#000000;stroke-width:1"  cx="0"  cy="0"  r="15" />  </g>  </g> </svg>';
	//zoom cursor self-drawn
	let zoomCursor = `url("data:image/svg+xml,%3Csvg width='32' height='32' xmlns='http://www.w3.org/2000/svg'%3E%3Cg%3E%3Ctitle%3ELayer 1%3C/title%3E%3Cellipse stroke='%23000' fill='url(%23svg_17)' stroke-width='2' stroke-dasharray='null' stroke-opacity='null' cx='13.95456' cy='12.81821' id='svg_4' rx='6.31813' ry='6.31813'/%3E%3Cline fill='none' stroke='%23000' stroke-width='2' stroke-dasharray='null' stroke-opacity='null' fill-opacity='null' x1='18.54543' y1='17.59089' x2='25.77965' y2='24.82511' id='svg_6' stroke-linejoin='null' stroke-linecap='null'/%3E%3Cline transform='rotate(90 25.2126 8.452)' stroke-linecap='null' stroke-linejoin='null' id='svg_1' y2='10.98273' x2='25.21259' y1='5.92127' x1='25.21259' stroke-dasharray='null' stroke='%2300bf00' fill='none'/%3E%3Cline stroke-linecap='null' stroke-linejoin='null' id='svg_2' y2='10.98273' x2='25.21259' y1='5.92127' x1='25.21259' stroke-dasharray='null' stroke='%2300bf00' fill='none'/%3E%3C/g%3E%3Cdefs%3E%3CradialGradient r='2.07054' cy='0.01172' cx='0.47656' spreadMethod='pad' id='svg_17'%3E%3Cstop offset='0.00391' stop-opacity='0.95703' stop-color='%23dbf9f9'/%3E%3Cstop offset='1' stop-opacity='0.10938' stop-color='%23ff56aa'/%3E%3C/radialGradient%3E%3C/defs%3E%3C/svg%3E") 16 16, zoom-in`;

	//enums
	let zoomModes = { fit: 0, width: 1, height: 2, full: 3 } //viewing images (zoom button)
	let clickModes = { cont: 0, img: 1, preview: 2, rect: 3, navi: 4, close: 5, setting: 6, wide: 7, height: 8, real: 9, window: 10 }; //event delegation button click

	let prevImg, //preview image
			img, //original image object
			zoomCont = 0, //zoom image div container
			zoomImg, //actually zoomed image
			highRec //highlight rectangle
	;
	let diag; //floating dialog div
	let lastZoomT = 0, //scroll zoom debouncer by timer
			lastResize = 0; //window resize debouncer by timer

	// let prevImg.offsetWidth, prevImg.offsetHeight, zoomImg.offsetWidth, zoomImg.offsetHeight, window.innerWidth, window.innerHeight;
	let userSet = { //user settings, saved/loaded by GM.
			opacityPrev: 50, //percent, drag-preview image opacity
			opacityHigh: 50, //percent, highlight rectangle opacity
			marginPrev: 10, //percent, margin around preview image
			lastZoom: zoomModes.fit //last zoom mode clicked = auto when open zoom
	};

	//%%%helper functions

	//set CSS style from array
	function setCss(el, arr) {
			if (!el) return;
			for (let i in arr) {
					el.style[i] = arr[i];
			}
	}

	//simple collision check, (x,y) inside obj-rectangle
	function coordInObj(x, y, obj) {
			let cImgOff = { left: obj.getBoundingClientRect().left + window.scrollY, top: obj.getBoundingClientRect().top + window.scrollY };

			return (x >= cImgOff.left && x <= cImgOff.left + prevImg.offsetWidth &&
					y >= cImgOff.top && y <= cImgOff.top + prevImg.offsetHeight);
	}

	//%%% DOM manipulation

	//moves zoomed image to (tx,ty)
	function moveToXY(tx, ty) {

			//highlight rect size, window/zoom scaled to preview size,
			let highr = {
					w: window.innerWidth / zoomImg.offsetWidth * prevImg.offsetWidth,
					h: window.innerHeight / zoomImg.offsetHeight * prevImg.offsetHeight
			};

			//percentage size scaled to preview image
			let highRat = {
					w: highr.w / prevImg.offsetWidth / 2,
					h: highr.h / prevImg.offsetHeight / 2
			};

			//real xy-position of preview image
			let cImgOff = {
					left: prevImg.getBoundingClientRect().left + window.scrollY,
					top: prevImg.getBoundingClientRect().top + window.scrollY
			};

			//relative shift of image (rect & zoom-image)
			let rel = { x: 0, y: 0 }

			if (zoomImg.offsetWidth < window.innerWidth) { //vertical center image if all fits on screen
					rel.x = 0;
			} else {
					rel.x = (tx - cImgOff.left) / prevImg.offsetWidth; //relative offset to preview, 0 in center
					rel.x = rel.x > 1 - highRat.w ? 1 - highRat.w : rel.x < highRat.w ? highRat.w : rel.x; //limit highlight-rec fit
					rel.x -= 0.5;
					rel.x = rel.x > 0.5 ? 0.5 : rel.x < -0.5 ? -0.5 : rel.x; //limit [-0.5,0.5]
			}

			if (zoomImg.offsetHeight < window.innerHeight) { //horizontal center image if all fits on screen
					rel.y = 0;
			} else { //same as rel.x
					rel.y = (ty - cImgOff.top) / prevImg.offsetHeight;
					rel.y = rel.y > 1 - highRat.h ? 1 - highRat.h : rel.y < highRat.h ? highRat.h : rel.y;
					rel.y -= 0.5;
					rel.y = rel.y > 0.5 ? 0.5 : rel.y < -0.5 ? -0.5 : rel.y;
			}

			//relative shift scaled to zoom image px.
			let offZoom = {
					x: -rel.x * zoomImg.offsetWidth,
					y: -rel.y * zoomImg.offsetHeight
			};

			//fix for margin:auto
			if (zoomImg.offsetWidth > window.innerWidth) offZoom.x -= (zoomImg.offsetWidth - window.innerWidth) / 2;
			// if(zoomImg.offsetHeight>window.innerHeight)offY-=(zoomImg.offsetHeight-window.innerHeight)/2; //margin auto only confuses X

			//moving zoom image
			zoomImg.style.transform = "translate(" + offZoom.x + "px," + offZoom.y + "px)";

			//moving/resizing highlight rectangle
			setCss(highRec, {
					width: highr.w + "px",
					height: highr.h + "px",
					transform: "translate(" + rel.x * prevImg.offsetWidth + "px," + rel.y * prevImg.offsetHeight + "px)"
			});
	}

	//fast zoom mode application
	function fullViewZoom(mode = zoomModes.fit, updateSet = true) {
			let sty = "";
			switch (mode) {
					case zoomModes.fit:
							if ((img.offsetWidth / img.offsetHeight) > (window.innerWidth / window.innerHeight)) return fullViewZoom(zoomModes.width);
							else fullViewZoom(zoomModes.height);
							break;
					case zoomModes.width:
							sty = { width: "100%", height: "auto", transform: "" };
							break;
					case zoomModes.height:
							sty = { height: "100%", width: "auto", transform: "" };
							break;
					case zoomModes.full:
							sty = { height: "auto", width: "auto", transform: "" };
							break;
			}

			setCss(zoomImg, sty);

			if (updateSet) {
					userSet.lastZoom = mode;
					GM.setValue("settings", JSON.stringify(userSet));
			}

	}

	//resized preview image,triggered at start and resize-window
	function resizePreview() {
			setCss(prevImg, {
					inset: userSet.marginPrev + "%",
					width: "unset",
					height: "unset",
					"max-width": (100 - userSet.marginPrev * 2) + "%",
					"max-height": (100 - userSet.marginPrev * 2) + "%"
			});

			setCss(highRec, { opacity: userSet.opacityHigh });
	}
	function checkURL(step=0){
			let tmpImage,mtch,imgsrc;
			switch(step){
					default:
					case 0: //check original image for dimension, min 400x400, thumbs are 96*96px, browse uses 340px heightupdateZoom(img.src);
							updateZoom(img.src); //use original image as placeholder and basis

							tmpImage = new Image();
							tmpImage.onload = function() {
									if(this.height<400|this.width<400){ //too small
											checkURL(1);
									}
									//all fine!
							};
							tmpImage.onerror = function() { //something wrong
									checkURL(1);
							};
							tmpImage.src=img.src;
							break;
					case 1: //try to change img url to access download images
							mtch=img.src.match(/(^.*?)(\/..?\/(?:crop|fill|fit)\/.*?)?(\?token=.*)/);
							imgsrc= mtch[1]+mtch[3];

							tmpImage = new Image();
							tmpImage.onload = function() {//image has accessible original image
									updateZoom(imgsrc);
									//switch to original fullview image
							};
							tmpImage.onerror = function() { //mostly response "forbidden", if direct access not enabled
									checkURL(2);
							};
							tmpImage.src=imgsrc;
							break;
					case 2: //use default scaler 1200px in height and width
							imgsrc=img.src.replace(/\/(crop|fill|fit)\/.*?\//,"/fit/w_1200,h_1200/");

							tmpImage = new Image();
							tmpImage.onload = function() {
									updateZoom(imgsrc);
									//switch to 1200px fit image
							};
							tmpImage.onerror = function() {
									checkURL(3); //if original is smaller than 1200, requests to fill are "forbidden".
							};
							tmpImage.src=imgsrc;
							break;
					case 3: //use default scaler 800px in height and width
							imgsrc=img.src.replace(/\/(crop|fill|fit)\/.*?\//,"/fit/w_800,h_800/");

							tmpImage = new Image();
							tmpImage.onload = function() {
									updateZoom(imgsrc);
									//switch to 800px fit image
							};
							tmpImage.onerror = function() {
									//no high-R image, image should still be img.src
									console.log("fallback original resolution", imgsrc,img.src);
							};
							tmpImage.src=imgsrc;
							break;
			};

	}

	function updateZoom(imgsrc) {
			if(!img)return;
			zoomImg.src = imgsrc ;
			prevImg.src = imgsrc ;
			resizePreview(); //adapt resize image
			fullViewZoom(userSet.lastZoom); //default zoom
	}

	//adds all elements to DOM (initial)
	function addElements(){
			//zoom container
			if(zoomCont)return;
			zoomCont = document.createElement("div");
			zoomCont.id = "dA_zoomImage_zoomCont";
			zoomCont.setAttribute("clickMode", clickModes.cont);
			document.body.appendChild(zoomCont);

			//zoomed image
			zoomImg = document.createElement("img");
			zoomImg.src ="";//imgsrc;
			zoomImg.id = "dA_zoomImage_zoomImg";
			zoomImg.setAttribute("clickMode", clickModes.img);
			setCss(zoomImg, { width: window.innerWidth + "px", height: window.innerHeight + "px" });
			zoomCont.appendChild(zoomImg);

			//preview image
			prevImg = document.createElement("img");//img.cloneNode(true);
			prevImg.setAttribute("clickMode", clickModes.preview);
			prevImg.id = "dA_zoomImage_prevImg";
			zoomCont.appendChild(prevImg);

			//highlight rectangle
			highRec = document.createElement("div");
			highRec.setAttribute("clickMode", clickModes.rect);
			highRec.id = "dA_zoomImage_highR";
			zoomCont.appendChild(highRec);

			//button container
			let navi = document.createElement("div");
			navi.setAttribute("clickMode", clickModes.navi);
			navi.id = "dA_zoomImage_navigation";
			zoomCont.appendChild(navi);

			//button container buttons (close)
			let closebut = document.createElement("div");
			closebut.title = "Close Zoom";
			closebut.setAttribute("clickMode", clickModes.close);
			closebut.innerHTML = "╳";
			navi.appendChild(closebut);

			//button container buttons (settings)
			let but = document.createElement("div");
			but.title = "dragFullscreen Settings";
			but.setAttribute("clickMode", clickModes.setting);
			but.innerHTML = imgGear;
			navi.appendChild(but);

			//button container buttons (fit width)
			let widebut = document.createElement("div");
			widebut.title = "Zoom Width";
			widebut.setAttribute("clickMode", clickModes.wide);
			widebut.innerHTML = "↔";
			navi.appendChild(widebut);

			//button container buttons (fit height)
			let highbut = document.createElement("div");
			highbut.title = "Zoom Height";
			highbut.setAttribute("clickMode", clickModes.height);
			highbut.innerHTML = "↕";
			navi.appendChild(highbut);

			//button container buttons (fit window)
			let fitbut = document.createElement("div");
			fitbut.title = "Zoom Fit Window";
			fitbut.setAttribute("clickMode", clickModes.window);
			fitbut.innerHTML = "⭾";
			navi.appendChild(fitbut);

			//button container buttons (full size)
			let realbut = document.createElement("div");
			realbut.title = "Real size";
			realbut.setAttribute("clickMode", clickModes.real);
			realbut.innerHTML = "⤧";
			navi.appendChild(realbut);

			//settings dialog
			diag = document.createElement("div");
			diag.id = "dA_zoomImage_settings";
			diag.style.display = "none";
			diag.innerHTML = `<form style='display:grid;grid-template-columns:auto auto 30px;grid-gap: 5px;'>
									<label for="previewOpacity">Preview Opacity (%):</label>
									<input type="range" min="0" max="100" value=${userSet.opacityPrev} name="previewOpacitySlider">
									<input type="text" value="${userSet.opacityPrev}" name="previewOpacity">
									<label for="highOpacity">Highlight Opacity (%):</label>
									<input type="range" min="0" max="100" value=${userSet.opacityHigh} name="highOpacitySlider">
									<input type="text" value="${userSet.opacityHigh}" name="highOpacity">
									<label for="previewMargin">Preview Margin (%):</label>
									<input type="range" min="0" max="40" value=${userSet.marginPrev} name="previewMarginSlider">
									<input type="text" value="${userSet.marginPrev}" name="previewMargin">
							</form><div id="dA_zoomImage_settings_OK">Save</div>`.replace(/\s\s+/g, "");
			document.body.appendChild(diag);

			document.getElementsByName("previewOpacitySlider")[0].addEventListener("input", function(ev) {
					document.getElementsByName("previewOpacity")[0].value = this.value;
			});
			document.getElementsByName("highOpacitySlider")[0].addEventListener("input", function(ev) {
					document.getElementsByName("highOpacity")[0].value = this.value;
			});
			document.getElementsByName("previewMarginSlider")[0].addEventListener("input", function(ev) {
					document.getElementsByName("previewMargin")[0].value = this.value;
			});

			document.getElementsByName("previewOpacity")[0].addEventListener("input", function(ev) {
					if (this.value > 100) this.value = 100;
					if (this.value < 0) this.value = 0;
					document.getElementsByName("previewOpacitySlider")[0].value = this.value;
			});

			document.getElementsByName("highOpacity")[0].addEventListener("input", function(ev) {
					if (this.value > 100) this.value = 100;
					if (this.value < 0) this.value = 0;
					document.getElementsByName("highOpacitySlider")[0].value = this.value;
			});

			document.getElementsByName("previewMargin")[0].addEventListener("input", function(ev) {
					if (this.value > 40) this.value = 40;
					if (this.value < 0) this.value = 0;
					document.getElementsByName("previewMarginSlider")[0].value = this.value;
			});


			//OK settings dialog button (save settings, apply changes, hide dialog)
			var okbut = document.querySelector("#dA_zoomImage_settings_OK");
			okbut.addEventListener("click", function(ev) {
					ev.stopPropagation();
					ev.preventDefault();
					userSet.opacityPrev = document.querySelector("#dA_zoomImage_settings input[name=previewOpacity]").value;
					userSet.opacityHigh = document.querySelector("#dA_zoomImage_settings input[name=highOpacity]").value;
					userSet.marginPrev = document.querySelector("#dA_zoomImage_settings input[name=previewMargin]").value;

					userSet.opacityPrev = userSet.opacityPrev > 100 ? 100 : userSet.opacityPrev < 0 ? 0 : userSet.opacityPrev;
					userSet.opacityHigh = userSet.opacityHigh > 100 ? 100 : userSet.opacityHigh < 0 ? 0 : userSet.opacityHigh;
					userSet.marginPrev = userSet.marginPrev > 40 ? 40 : userSet.marginPrev < 0 ? 0 : userSet.marginPrev;

					diag.style.display = "none";

					document.querySelector("#dA_zoomImage_prevImg").style.opacity = userSet.opacityPrev + "%";
					document.querySelector("#dA_zoomImage_highR").style.opacity = userSet.opacityHigh + "%";
					resizePreview();

					GM.setValue("settings", JSON.stringify(userSet));
			});
			zoomCont.addEventListener("mouseup", function(ev) { //event delegation
					ev.preventDefault();
					switch (parseInt(ev.target.getAttribute("clickMode"))) {
							case clickModes.cont: //close zoom when clicking on free space
									//free space=outside zoom and preview-img
									if (coordInObj(ev.pageX, ev.pageY, prevImg) && coordInObj(ev.pageX, ev.pageY, zoomImg)) { //if inside, "mouseup" hide preview and highlight
											ev.stopPropagation();
											prevImg.style.visibility = 'hidden';
											highRec.style.visibility = 'hidden';
											break;
									}
									//no break, close window if free space ↓
									// falls through (eslint comment)
							case clickModes.close: //close zoom
									zoomCont.style.display = 'none';
									diag.style.display = "none";
									ev.stopPropagation();
									break;
							case clickModes.wide: //wide zoom mode button
									fullViewZoom(zoomModes.width);
									ev.stopPropagation();
									break;
							case clickModes.window: //window fit zoom mode button
									fullViewZoom(zoomModes.fit);
									ev.stopPropagation();
									break;
							case clickModes.height: //height zoom mode button
									fullViewZoom(zoomModes.height);
									ev.stopPropagation();
									break;
							case clickModes.real: //wide zoom mode button
									fullViewZoom(zoomModes.full);
									ev.stopPropagation();
									break;
							case clickModes.img: //basically mouseup, remove preview/highlight
							case clickModes.rect:
							case clickModes.preview:
									prevImg.style.visibility = 'hidden';
									highRec.style.visibility = 'hidden';
									ev.stopPropagation();
									break;
							case clickModes.setting:
									document.querySelector("#dA_zoomImage_settings input[name=previewOpacity]").value = userSet.opacityPrev;
									document.querySelector("#dA_zoomImage_settings input[name=highOpacity]").value = userSet.opacityHigh;
									document.querySelector("#dA_zoomImage_settings input[name=previewMargin]").value = userSet.marginPrev;
									diag.style.display = "block";
									ev.stopPropagation();
									break;
							case NaN: //no clickmode = propagate
									break;
							default: //unused clickmodes
									ev.stopPropagation();
									break;
					}
			});

			//zoom img at mouse position
			zoomCont.addEventListener('wheel', function(ev) {
					ev.preventDefault();
					ev.stopPropagation();

					if (Date.now() - lastZoomT < 20) return; //wheel at 50evt/s
					lastZoomT = Date.now();

					if (ev.deltaY < 0) { //first zoom,  then scroll. bit jitterish?
							zoomImg.style.width = zoomImg.clientWidth * 1.1 + "px";
							zoomImg.style.height = zoomImg.clientHeight * 1.1 + "px";
					} else if (ev.deltaY > 0) {
							zoomImg.style.width = zoomImg.clientWidth / 1.1 + "px";
							zoomImg.style.height = zoomImg.clientHeight / 1.1 + "px";
					}
					moveToXY(ev.pageX, ev.pageY);
			});

			//moveing cursor = moving image
			zoomCont.addEventListener("mousemove", function(ev) {
					ev.preventDefault();
					ev.stopPropagation();

					if (Date.now() - lastZoomT < 50) return; //move at 20evt/s debounce
					lastZoomT = Date.now();

					moveToXY(ev.pageX, ev.pageY);
			});

			//show preview/highrect on click/drag
			zoomCont.addEventListener("mousedown", function(ev) {
					ev.preventDefault();
					switch (parseInt(ev.target.getAttribute("clickMode"))) {
							case clickModes.img:
							case clickModes.rect:
							case clickModes.preview:
									prevImg.style.visibility = 'visible';
									highRec.style.visibility = 'visible';
									ev.stopPropagation();
									break;
							case NaN:
							default:
									break;
					}
			});

			//window resize = resize preview
			document.addEventListener("resize", function(ev) {
					if (Date.now() - lastResize < 100) return; //resize at 10evt/s debounce
					lastResize = Date.now();

					resizePreview();
			});
	}

	//watchdog function on interval 1s
	//dA browsing is js based, so DOM might change and watchdog reacts if elements are applicable.
	function addDragger(mutationList, observer) {
			//  document.querySelectorAll("img[src*=wix]")
			let imgs = document.querySelectorAll("img[src*=wix]:not([dA_zoomImage])");//document.querySelector("div[data-hook=art_stage] img:not([dA_zoomImage])"); //react on art_stage-divs, once per element
			imgs.forEach(el=>{
					if (el != null) {
							el.setAttribute("dA_zoomImage", 1);
							// el.setAttribute("draggable", "false");
							img = el;

							//show zoom window
							el.addEventListener("click", function(ev) {
									if (!el.hasAttribute("fetchpriority")&&!ev.shiftKey)return;

									ev.stopPropagation();
									ev.preventDefault();
									img=ev.target;
									checkURL();
									zoomCont.style.display = 'block';
									prevImg.style.visibility = 'hidden';
									highRec.style.visibility = 'hidden';
							});
					}
			});
			addElements();
			//add elements;
			//addZoomElements();
	}


	//load settings, add custom style,  execute script
	GM.getValue("settings", "").then((val) => {
			if (val == "") return;
			userSet = JSON.parse(val);
			if (!userSet.hasOwnProperty("marginPrev")) userSet.marginPrev = 10; //default, backwards compability to v1.1
			if (!userSet.hasOwnProperty("lastZoom")) userSet.lastZoom = zoomModes.fit; //default, backwards compability to v1.5
	}).finally(() => {
			let style = document.createElement('style');
			style.textContent = `
	#dA_zoomImage_zoomImg{position:absolute;z-index:90;inset:0;margin:auto;}
	#dA_zoomImage_prevImg{position:fixed;visibility:hidden;cursor:move;z-index:90;margin:auto;opacity:${userSet.opacityPrev}%;}
	#dA_zoomImage_highR{position:fixed;border:2px solid #475c4daa;box-shadow:0px 0px 10px white inset;visibility:hidden;box-sizing:border-box;z-index:91;margin:auto;inset:0;opacity:${userSet.opacityHigh}%;}
	#dA_zoomImage_navigation{position:fixed;top:0;right:0;z-index:99;opacity:0.2}
			#dA_zoomImage_navigation:hover{opacity:1}
	#dA_zoomImage_navigation>div{cursor:pointer;background-color: var(--g-bg-tertiary);border-radius: 50px;
		line-height: 27px;text-align: center;width:30px;height:30px;margin:10px;padding:5px;font-size: larger;}
	#dA_zoomImage_navigation>div * {pointer-events: none;}
	#dA_zoomImage_zoomCont{position: fixed;z-index: 99;top: 0;left: 0;right: 0;bottom: 0;background: var(--g-bg-primary);display:none;}
	#dA_zoomImage_settings {position: fixed;top: 0;left: 0;right: 0;bottom: 0;z-index: 99;width: 400px;height: 120px;margin: auto;background-color: var(--g-bg-primary);padding: 10px;border-radius: 10px;border: 5px ridge var(--typography-tertiary-light);
		box-shadow: 3px 3px 5px black;}
	#dA_zoomImage_settings_OK{position: absolute;right: 10px;bottom: 10px;color: var(--typography-primary);padding: 5px;border-radius: 5px;border: 1px solid var(--typography-primary);cursor: pointer;}
	#dA_zoomImage_settings_OK:hover{box-shadow: inset 0 0 6px var(--typography-primary);}
			.shift_pressed img[da_zoomimage]{cursor:${zoomCursor}!important}
			img[da_zoomimage][fetchpriority]{cursor:${zoomCursor}!important}
`.replace(/\s\s+/g, ""); //indentation removed from string
			document.head.appendChild(style);

			document.body.addEventListener("keydown",(ev)=>{if(ev.shiftKey)document.body.classList.add("shift_pressed");;});
			document.body.addEventListener("keyup",(ev)=>{if(!ev.shiftKey);document.body.classList.remove("shift_pressed");});

			//start watchdog, 1s interval.
			const observer = new MutationObserver(addDragger);
			observer.observe(document.body,{ childList: true, subtree: true });

	});



})();