Center Image

Improved controls for images directly opened with your browser - hotkeys & resizing & visuals

目前为 2021-12-27 提交的版本。查看 最新版本

// ==UserScript==
// @name          Center Image
// @namespace     CenterImage
// @author        Owyn
// @version       1.96
// @description   Improved controls for images directly opened with your browser - hotkeys & resizing & visuals
// @supportURL    https://github.com/Owyn/Center_Image/issues
// @homepage      https://github.com/Owyn/Center_Image
// @run-at        document-end
// @noframes
// @grant         GM.getValue
// @grant         GM.setValue
// @grant         GM_registerMenuCommand
// @match         http://*/*
// @match         https://*/*
// @match         file:///*/*
// ==/UserScript==

"use strict";

var images = document.images;
if (!images || images.length !== 1 || images[0].src !== location.href) 
{
	return false;
}

if (typeof GM_registerMenuCommand !== "undefined")
{
	GM_registerMenuCommand("Center Image Configuration", cfg, "n");
}

var rescaled = 0;
var iot = 0, iol = 0;
var i = images[0];

var theStyle;
function makeimage()
{
	if(typeof cfg_js !== "string") {setTimeout(function() { makeimage(); }, 11); return false;} // lets wait for async
	if(cfg_bgclr)
	{
		document.body.bgColor = cfg_bgclr;
		if(document.head){document.head.innerHTML = "";} // remove FireFox background
	}
	document.body.innerHTML = "<style id=img_style>img { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background-color: "+cfg_bgclr+"}</style>"; // center image & change loading image color from white in Chrome
	theStyle = document.body.querySelector("#img_style");
	i.style = ""; // chrome has lots of crap there
	document.body.style = "";
	theStyle.sheet.rules[0].style.margin = "auto"; // center image
	document.body.style.margin = "0px";
	document.body.appendChild(i);
	i.addEventListener("mousedown", onmousedown, true);
	i.addEventListener("click", rescale, true);
	i.addEventListener("auxclick", rescale, true);
	window.addEventListener("keydown", onkeydown, true);
	window.addEventListener("scroll", onscroll, false);
	window.addEventListener("resize", onresize, true);
	onVisibilityChange();
}

function onVisibilityChange()
{
	if (document.visibilityState === 'visible')
	{
		if(i)
		{
			autoresize();
			document.removeEventListener('visibilitychange', onVisibilityChange);
		}
	}
}
document.addEventListener("visibilitychange", onVisibilityChange);

var scrolledY, scrolledX;
function onscroll()
{
	scrolledY = window.pageYOffset;
	scrolledX = window.pageXOffset;
}

var FireFox = ((navigator.userAgent.indexOf('Firefox') != -1) ? true : false);
function onresize() // doesn't let image change back to "fit to window" in chrome & in both helps keep scroll progress
{
	if(rescaled != 1)
	{
		let wasFilled = (rescaled === 2 ? true : false);
		rescaled = 1;
		let chromeplsstopX, chromeplsstopY;
		if(!FireFox){ chromeplsstopX = window.pageXOffset; chromeplsstopY = window.pageYOffset;}
		rescale(null, wasFilled);
		if(!FireFox){ window.scrollTo(chromeplsstopX,chromeplsstopY);}
	}
}

function changeCursor()
{
	//i.style.margin = "auto";
	if(rescaled === 0) // original
	{
		if(orgImgWidth == window.innerWidth || orgImgHeight == window.innerHeight) // perfect fit, can't resize
		{
			i.style.cursor = "";
		}
		else if (orgImgWidth > window.innerWidth || orgImgHeight > window.innerHeight)
		{
			i.style.cursor = "zoom-out";
		}
		else
		{
			i.style.cursor = "zoom-in";
		}
	}
	else if(rescaled === 2) // fill
	{
		if(orgImgWidth == window.innerWidth && orgImgHeight == window.innerHeight) // perfect fit, can't resize
		{
			i.style.cursor = "";
		}
		else if (orgImgWidth > i.width)
		{
			i.style.cursor = "zoom-in";
		}
		else
		{
			i.style.cursor = "zoom-out";
		}
	}
	else // if(rescaled === 1) // fit
	{
		if(orgImgWidth == window.innerWidth || orgImgHeight == window.innerHeight) // perfect fit, can't resize
		{
			i.style.cursor = "";
		}
		else if (orgImgWidth > i.width)
		{
			i.style.cursor = "zoom-in";
		}
		else
		{
			i.style.cursor = "zoom-out";
		}
	}

	if(i.height > window.innerHeight) // image pushing out-of-screen browser bug fix
	{
		theStyle.sheet.rules[0].style.margin = "0px auto";
	}
	else
	{
		theStyle.sheet.rules[0].style.margin = "auto";
	}
	i.style.margin = ""; // chrome pls stop
}

function onmousedown()
{
	if(i.offsetLeft > 0){iol = i.offsetLeft;}
	if(i.offsetTop > 0){iot = i.offsetTop;}

	if(event.which === 2) // middle mouse Chrome fix
	{
		event.preventDefault();
		event.stopPropagation();
		return;
	}
}

function rescale(event, fill)
{
	let ex,ey;
	if(event) // mouse click
	{
		if (typeof event.y === "undefined") // Firefox
		{
			ex = event.clientX;
			ey = event.clientY;
		}
		else
		{
			ex = event.x;
			ey = event.y;
		}
		ex -= iol;
		ey -= iot;
		if(event.which === 2) // middle mouse
		{
			fill = true;
		}
		else if(event.which === 3) // right mouse
		{
			return;
		}
		event.preventDefault();
		event.stopPropagation();
	}

	document.body.style.overflowX = '';
	document.body.style.overflowY = '';

	let scrollMax_Y = window.scrollMaxY || ((document.body.scrollHeight || document.documentElement.scrollHeight)- document.documentElement.clientHeight);
	let scrollMax_X = window.scrollMaxX || ((document.body.scrollWidth || document.documentElement.scrollWidth)- document.documentElement.clientWidth);

	let scrollProgressY = scrolledY / scrollMax_Y;
	let scrollProgressX = scrolledX / scrollMax_X;

	let unFilling = false;

	let sidesCMP;
	if(fill)
	{
		if(rescaled === 2) // to original
		{
			rescaled = 0;
			theStyle.sheet.rules[0].style.width = orgImgWidth + "px";
			theStyle.sheet.rules[0].style.height = orgImgHeight + "px";
		}
		else // fill
		{
			sidesCMP = (orgImgWidth / orgImgHeight) < (window.innerWidth / window.innerHeight);
			rescaled = 2;
		}
	}
	else
	{
		if(rescaled != 0) // to original
		{
			if(rescaled === 2) {unFilling = true;}
			rescaled = 0;
			theStyle.sheet.rules[0].style.width = orgImgWidth + "px";
			theStyle.sheet.rules[0].style.height = orgImgHeight + "px";
		}
		else // fit
		{
			sidesCMP = (orgImgWidth / orgImgHeight) > (window.innerWidth / window.innerHeight);
			rescaled = 1;
		}
	}

	if(rescaled != 0)
	{
		if(sidesCMP)
		{
			theStyle.sheet.rules[0].style.width = "100%";
			theStyle.sheet.rules[0].style.height = "auto";
			document.body.style.overflowX = 'hidden'; // we don't need unscrollable scrollbars if they appear
		}
		else
		{
			theStyle.sheet.rules[0].style.height = "100%";
			theStyle.sheet.rules[0].style.width = "auto";
			document.body.style.overflowY = 'hidden'; // we don't need unscrollable scrollbars if they appear
		}
	}

	changeCursor();

	if(event && (!unFilling && (!fill || (fill && (scrollMax_Y <= 0 && scrollMax_X <= 0))))) // left mouse click (fill-click with no scrollbars and not left click after middle click - else preserve scroll percentage)
	{
		let scale = Math.min((window.innerWidth / i.width), (window.innerHeight / i.height));
		window.scrollTo((ex / scale) - (window.innerWidth / 2), (ey / scale) - (window.innerHeight / 2));
	}
	else // keep percentage scroll progress for KB hotkeys
	{
		
		scrollMax_Y = window.scrollMaxY || ((document.body.scrollHeight || document.documentElement.scrollHeight)- document.documentElement.clientHeight);
		scrollMax_X = window.scrollMaxX || ((document.body.scrollWidth || document.documentElement.scrollWidth)- document.documentElement.clientWidth);

		window.scrollTo(Math.round(scrollProgressX * scrollMax_X), Math.round(scrollProgressY * scrollMax_Y));

		scrolledY = window.pageYOffset;
		scrolledX = window.pageXOffset;
	}
}

var orgImgWidth;
var orgImgHeight;

function autoresize()
{
	if(!document.head) // old fix for old chrome - let it be
	{
		document.lastChild.insertBefore(document.createElement("head"), document.body);
	}
	/*var link = document.createElement('link');
	link.type = 'image/x-icon';
	link.rel = 'shortcut icon';
	link.href = i.src;
	document.head.appendChild(link);*/ // favicon
	if(FireFox) // chrome already does ~this
	{
		var title = i.src.substr(i.src.lastIndexOf("/")+1);
		if(title.indexOf("?") != -1)
		{
			title = title.substr(0, title.indexOf("?"));
		}
		document.title = title + " (" + i.naturalWidth + "x" + i.naturalHeight + ")"; // title
	}
	
	orgImgWidth = Math.round(i.naturalWidth / window.devicePixelRatio);
	orgImgHeight = Math.round(i.naturalHeight / window.devicePixelRatio);

	let InitRescale = false; // directly opened image is already fit to window if it is bigger by the browser
	if(cfg_fitWH && orgImgHeight > window.innerHeight && orgImgWidth > window.innerWidth) // both scrollbars
	{
		InitRescale = false;
	}
	else if(cfg_fitB && (orgImgHeight > window.innerHeight || orgImgWidth > window.innerWidth)) // one scrollbar
	{
		InitRescale = false;
	}
	else if(cfg_fitS && orgImgHeight <= window.innerHeight && orgImgWidth <= window.innerWidth) // no scrollbars
	{
		InitRescale = true;
	}
	else
	{
		theStyle.sheet.rules[0].style.width = orgImgWidth + "px";
		theStyle.sheet.rules[0].style.height = orgImgHeight + "px";
	}
	if(InitRescale)
	{
		rescale(null, cfg_fill ? true : false);
	}
	else
	{
		changeCursor();
	}
	if(cfg_js){eval(cfg_js);}
}

// hotkeys
if (typeof KeyEvent === "undefined")
{
	var KeyEvent = {
		DOM_VK_SPACE: 32,
		DOM_VK_LEFT: 37,
		DOM_VK_UP: 38,
		DOM_VK_RIGHT: 39,
		DOM_VK_DOWN: 40,
		DOM_VK_A: 65,
		DOM_VK_D: 68,
		DOM_VK_P: 80,
		DOM_VK_Q: 81,
		DOM_VK_S: 83,
		DOM_VK_W: 87,
		DOM_VK_NUMPAD2: 98,
		DOM_VK_NUMPAD4: 100,
		DOM_VK_NUMPAD5: 101,
		DOM_VK_NUMPAD6: 102,
		DOM_VK_NUMPAD8: 104,
		DOM_VK_F5: 116,
		DOM_VK_TAB: 9,
		DOM_VK_ENTER: 13
	};
}

function cancelEvent(a)
{
	a = a ? a : window.event;
	if (a.stopPropagation)
	{
		a.stopPropagation();
	}
	if (a.preventDefault)
	{
		a.preventDefault();
	}
	a.cancelBubble = true;
	a.cancel = true;
	a.returnValue = false;
	return false;
}

function scroll_space(a, b)
{
	var by = Math.round((b ? window.innerHeight : window.innerWidth) * 0.50 * (a ? -1 : 1));
	if(!b)
	{
		window.scrollBy(0, by);
	}
	else
	{
		window.scrollBy(by, 0);
	}
}

function onkeydown (b)
{
	var a = (window.event) ? b.keyCode : b.which;
	
	if (a != KeyEvent.DOM_VK_SPACE && (b.altKey || b.ctrlKey || b.metaKey))
	{
		return;
	}
	
	var by = Math.round(window.innerHeight * 0.10);
	
	switch (a)
	{
	case KeyEvent.DOM_VK_RIGHT:
	case KeyEvent.DOM_VK_D:
	case KeyEvent.DOM_VK_NUMPAD6:
		window.scrollBy(by, 0);
		cancelEvent(b);
		break;
	case KeyEvent.DOM_VK_LEFT:
	case KeyEvent.DOM_VK_A:
	case KeyEvent.DOM_VK_NUMPAD4:
		window.scrollBy(by * -1, 0);
		cancelEvent(b);
		break;
	case KeyEvent.DOM_VK_W:
	case KeyEvent.DOM_VK_NUMPAD8:
		window.scrollBy(0, by * -1);
		cancelEvent(b);
		break;
	case KeyEvent.DOM_VK_S:
	case KeyEvent.DOM_VK_NUMPAD2:
		window.scrollBy(0, by);
		cancelEvent(b);
		break;
	case KeyEvent.DOM_VK_SPACE:
		scroll_space(b.shiftKey, b.ctrlKey);
		cancelEvent(b);
		break;
	case KeyEvent.DOM_VK_TAB:
	case KeyEvent.DOM_VK_ENTER:
		rescale(null, true);
		cancelEvent(b);
		break;
	case KeyEvent.DOM_VK_Q:
	case KeyEvent.DOM_VK_NUMPAD5:
		rescale(null, false);
		cancelEvent(b);
		break;
	case KeyEvent.DOM_VK_P:
		cfg();
		cancelEvent(b);
	}
}

var cfg_bgclr = "grey";
var cfg_fitWH = true;
var cfg_fitB = false;
var cfg_fitS = true;
var cfg_fill = false;
var cfg_js;

function cfg(){}

if (typeof GM !== "undefined" && typeof GM.getValue !== "undefined")
{
	async function loadCfg()
	{
			cfg_bgclr = await GM.getValue("bgColor", "grey");
			cfg_fitWH = await GM.getValue("fitWH", true);
			cfg_fitB = await GM.getValue("fitB", false);
			cfg_fitS = await GM.getValue("fitS", true);
			cfg_fill = await GM.getValue("fill", false);
			cfg_js = await GM.getValue("js", "");
		makeimage();
	}
	loadCfg();

	function $(id) {return document.getElementById(id);}

	cfg = function ()
	{
		function saveCfg()
		{
			GM.setValue("bgColor", $("ci_cfg_2_bgclr").value);
			GM.setValue("fitWH", $("ci_cfg_3_fitWH").checked);
			GM.setValue("fitB", $("ci_cfg_4_fitB").checked);
			GM.setValue("fitS", $("ci_cfg_5_fitS").checked);
			GM.setValue("fill", $("ci_cfg_7_fill").checked);
			GM.setValue("js", $("ci_cfg_6_js").value);
			alert("Configuration Saved");
			if($("ci_cfg_2_bgclr").value){document.body.bgColor = $("ci_cfg_2_bgclr").value;}else{document.body.removeAttribute("bgColor");}
		}
		if(i){
			i.removeEventListener("click", rescale, true);
			i.removeEventListener("auxclick", rescale, true);
			i.removeEventListener("mousedown", onmousedown, true);
		}
		window.removeEventListener("keydown", onkeydown, true);
		if(document.head){document.head.innerHTML = "";}
		document.body.innerHTML = "";
		var div = document.createElement("div");
		div.style.margin = "11% auto";
		div.style.width = "444px";
		div.style.border = "solid 1px black";
		div.style.color = "black";
		div.style.background = "silver";
		div.innerHTML = "<b><center>Configuration</center></b>"
		+ "<br><input id='ci_cfg_2_bgclr' type='text' size='6'> Background color (empty = default)"
		+ "<br><br>Fit to window images:" + " ( Fill to window instead <input id='ci_cfg_7_fill' type='checkbox'> )"
		+ "<br><br><input id='ci_cfg_3_fitWH' type='checkbox'> Larger than window both vertically and horizontally"
		+ "<br><br><input id='ci_cfg_4_fitB' type='checkbox'> Larger than window either vertically or horizontally"
		+ "<br><br><input id='ci_cfg_5_fitS' type='checkbox'> Smaller than window"
		+ "<br><br><center>Custom JS Action:<textarea id='ci_cfg_6_js' style='margin: 0px; width: 400px; height: 50px;'></textarea>"
		+ "<br><input id='ci_cfg_save' type='button' value='Save configuration'></center>";
		document.body.appendChild(div);
		$("ci_cfg_2_bgclr").value = cfg_bgclr;
		$("ci_cfg_3_fitWH").checked = cfg_fitWH;
		$("ci_cfg_4_fitB").checked = cfg_fitB;
		$("ci_cfg_5_fitS").checked = cfg_fitS;
		$("ci_cfg_7_fill").checked = cfg_fill;
		$("ci_cfg_6_js").value = cfg_js;
		$("ci_cfg_save").addEventListener("click", saveCfg, true);
	}
}
else
{
	cfg_js = "";
	cfg = function ()
	{
		alert("Sorry, Chrome userscripts in native mode can't have configurations! You need to install TamperMonkey userscript manager extension. (it's very good)");
	}
	
}