// ==UserScript==
// @name 画像のホバーで拡大
// @description Shift+H:オンオフ切替 y:ホバー中の画像をyandexで画像検索 a:twitterで画像をトリミングさせない
// @version 0.1.2
// @run-at document-idle
// @match *://*/*
// @exclude *://*.5ch.net/*
// @noframes
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @namespace https://greasyfork.org/users/181558
// @downloadURL none
// ==/UserScript==
(function() {
const hoverTimeDefault = 3; // 拡大するタイミング 0:オフ 1:画像に乗った瞬間 2-:画像に乗せてn/60秒静止したら
const defaultDelay = 3; // hoverTimeDefaultが0で手動オン時に拡大するタイミング 1:画像に乗った瞬間 2-:画像に乗せてn/60秒静止したら
const marginH = 100; // 元絵と拡大画像の横の余白px
const CONFIRM_FOR_Y = 1; // 1:yキーでyandex検索をする時URLの確認を求める
const KEY_SELECT_DELAY = "Shift+H"; // 設定キー
const POPUP_Z_INDEX = 10000; // ポップアップ画像がどくらい手前に来るか
const FORCE_USE_HALF_WIDTH = 0; // 1:ホバーズームで必ず画面の横半分のサイズ
const marginPe = 8;
const verbose = 0; // 1:debug
var domain = new URL(location.href).hostname;
var hovertime = pref(domain + " : delay") || hoverTimeDefault;
const minWidth = location.href.match(/twitter/) ? 1000 : (window.innerWidth / 2);
const minHeight = location.href.match(/twitter/) ? 1000 : (window.innerHeight / 2);
var mousex = 0;
var mousey = 0;
var lastEle = "";
var hovertimer = 0;
var poppedUrl = "";
var yandexUrl = "";
var zflag = pref("zflag") || 0;
setsize(zflag);
function setsize(z) {
if (/\/\/twitter\.com\//.test(location.href)) { GM_addStyle(`div{ background-size:${['cover','contain'][z]} !important;}`) }
}
document.addEventListener("keypress", e => {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.getAttribute('contenteditable') === 'true') return;
var key = (e.shiftKey ? "Shift+" : "") + (e.altKey ? "Alt+" : "") + (e.ctrlKey ? "Ctrl+" : "") + e.key;
if (key === "y" && poppedUrl) { // y::ホバー中の画像をyandex画像検索で検索
if (!CONFIRM_FOR_Y || window.confirm(yandexUrl + "\n\nを開きます。よろしいですか?")) window.open(yandexUrl);
return false;
}
if (key === "a") { // a::twitterで画像をトリミングさせないオンオフ
zflag = zflag ^ 1;
pref("zflag", zflag)
setsize(zflag)
return false;
}
if (key === KEY_SELECT_DELAY) { // Shift+H::ホバーの設定
elegeta('//img[@class="ignoreMe hzP"]').forEach(e => e.remove());
// let ret = proInput(domain + `\n\nのホバーズームの設定をしてください\n\n-1:オフ\n0:デフォルト(${hoverTimeDefault})\n1:画像に乗った瞬間\n2-:画像に乗せて数値/60秒静止したら\n空欄:設定削除(${hoverTimeDefault})\n\n`, pref(domain + " : delay") || defaultDelay);
let ret = hovertime > 0 ? -1 : defaultDelay;
if (ret === "") {
pref(domain + " : delay", "");
hovertime = hoverTimeDefault;
} else {
if (ret !== null) {
hovertime = ret || hoverTimeDefault;
pref(domain + " : delay", hovertime);
popup(`${domain}
${KEY_SELECT_DELAY}:ホバーズームを${hovertime>0?"有効":"無効"}にしました${hovertime>0?"("+hovertime+"/60秒)":""}`, hovertime > 0 ? "#6080ff" : "#808080")
}
}
}
});
var input_key_buffer = new Array();
document.addEventListener('keyup', function(e) {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.getAttribute('contenteditable') === 'true') return;
input_key_buffer[e.keyCode] = false;
}, false);
document.addEventListener('blur', function(e) { input_key_buffer.length = 0; }, false);
document.addEventListener('keydown', function(e) {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.getAttribute('contenteditable') === 'true') return;
input_key_buffer[e.keyCode] = true;
}, false);
document.addEventListener("mousemove", function(e) {
mousex = e.clientX;
mousey = e.clientY;
hovertimer = 0;
}, false);
document.addEventListener("wheel", function(e) {
hovertimer = 0;
}, false);
setInterval(onint, 16.667);
function onint() {
if (hovertime < 1) return;
hovertimer++;
let ele = document.elementFromPoint(mousex, mousey);
if (lastEle !== ele) {
hovertimer = 0;
if (poppedUrl) {
elegeta('//img[contains(@class,"ignoreMe hzP")]').forEach(e => e.remove());
elegeta('//span[contains(@class,"ignoreMe hzP")]').forEach(e => e.remove());
//elegeta('//img[@class="ignoreMe hzP"]').forEach(e => e.remove());
//elegeta('//span[@class="ignoreMe hzP"]').forEach(e => e.remove());
poppedUrl = "";
}
}
if (ele)
if (ele.tagName === "IMG" && hovertimer >= hovertime && poppedUrl == "" && (ele.clientWidth < minWidth || ele.clientHeight < minHeight)) {
poppedUrl = pe(ele);
yandexUrl = poppedUrl.match(/\;base64\,/i) ? null : "https://yandex.com/images/search?rpt=imageview&url=" + poppedUrl;
}
lastEle = ele;
}
function pe(a) {
let imgAspect = a.naturalWidth / a.naturalHeight; // svg等だとNaN 要.onload
let clientAspect = window.innerWidth / 2 / window.innerHeight;
if (a.parentNode.tagName == "A" && a.parentNode.href.match(/.mp4(\?.+)*$|.webm(\?.+)*$/i)) { // 動画
var ext = a.parentNode.href.match(/.mp4(\?.+)*$|.webm(\?.+)*$/i)[0]; //console.log(ext,a.parentNode.href)
var ve = document.createElement("video")
var panel = document.createElement("span");
panel.referrerpolicy = "no-referrer"; // 無効
panel.className = "ignoreMe hzP";
panel.appendChild(ve)
panel.src = a.parentNode.href;
panel.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 750, fill: 'forwards' });
ve.outerHTML = ``;
panel.maxWidth = "18%";
} else { // 画像
var panel = a.cloneNode(true);
panel.referrerpolicy = "no-referrer";
panel.className = "ignoreMe hzP";
if (a.parentNode.tagName == "A" && a.parentNode.href.match(/.png$|.jpg$|.jpeg$|.gif$|.bmp$|.webp$/i)) {
panel.src = a.parentNode.href;
} else if (a.parentNode.parentNode.tagName == "A" && a.parentNode.parentNode.href.match(/.png$|.jpg$|.jpeg$|.gif$|.bmp$|.webp$/i)) {
panel.src = a.parentNode.parentNode.href;
}
panel.src = panel.src.replace(/(https:\/\/pbs\.twimg\.com\/.*\?format=.+\&name=).+/, "$1orig");
}
// let peStyle='margin:5px; border-radius:3px; color:#ffffff; box-shadow:3px 3px 8px #0008; border:2px solid #fff;';
let peStyle = 'margin:2px; border-radius:3px; color:#ffffff; box-shadow:3px 3px 8px #0008; border:2px solid #fff;';
let boxPos = (mousey < (window.innerHeight / 2) ? "bottom:0px;" : "top:0px;") + ((mousex < document.documentElement.clientWidth / 2) ? "right:0px; " : "left:0px;");
panel.className = "ignoreMe hzP";
let aw = (mousex < document.documentElement.clientWidth / 2) ? (document.documentElement.clientWidth - (a.getBoundingClientRect()).right) : (a.getBoundingClientRect().left);
// if (((aw - marginH) / imgAspect) <= window.innerHeight && a.naturalWidth-marginH < aw - marginH ) {
if (imgAspect && (((aw - marginH) / imgAspect) <= window.innerHeight && (aw - marginH - marginPe) > a.width * 2.5)) {
panel.setAttribute("style", `all:initial;float:none; width:${aw-marginH-marginPe}px; height:${(aw-marginH)/imgAspect-marginPe}px;${boxPos} z-index:${POPUP_Z_INDEX}; position:fixed; ${peStyle}`); // 元絵の左右にくっつき最大
if (verbose) popup(`nx:${a.naturalWidth} ny:${a.naturalHeight} a:${Math.round(imgAspect*100)/100} くっつき`)
} else if (imgAspect && ((window.innerHeight * imgAspect - marginPe) < aw - marginH)) {
panel.setAttribute("style", `all:initial;float:none; width:${(window.innerHeight)*imgAspect-marginPe}px; height:${((window.innerHeight))-marginPe}px;${boxPos} z-index:${POPUP_Z_INDEX}; position:fixed; ${peStyle}`); // 縦目いっぱい
if (verbose) popup(`nx:${a.naturalWidth} ny:${a.naturalHeight} a:${Math.round(imgAspect*100)/100} 縦目いっぱい`)
} else if (!imgAspect || window.innerWidth * 0.48 / imgAspect - marginPe <= window.innerHeight) {
panel.setAttribute("style", `all:initial;float:none; width:${window.innerWidth*0.48-marginPe}px; height:${window.innerWidth*0.48/imgAspect-marginPe}px; ${boxPos} z-index:${POPUP_Z_INDEX}; position:fixed; ${peStyle}`); // 横48%
if (verbose) popup(`nx:${a.naturalWidth} ny:${a.naturalHeight} a:${Math.round(imgAspect*100)/100} width:${window.innerWidth*0.48-marginPe}px; height:${window.innerWidth*0.48/imgAspect-marginPe}px; 48%`)
} else {
// panel.setAttribute("style", `all:initial;float:none; max-width:${window.innerWidth*0.48}px; max-height:${window.innerWidth*0.48/imgAspect}px; width:${(window.innerHeight)*imgAspect-marginPe}px; height:${((window.innerHeight))-marginPe}px;${boxPos} z-index:${POPUP_Z_INDEX}; position:fixed; ${peStyle}`); // 縦目いっぱい
panel.setAttribute("style", `all:initial;float:none; width:${(window.innerHeight)*imgAspect-marginPe}px; height:${((window.innerHeight))-marginPe}px;${boxPos} z-index:${POPUP_Z_INDEX}; position:fixed; ${peStyle}`); // 縦目いっぱい
if (verbose) popup(`nx:${a.naturalWidth} ny:${a.naturalHeight} a:${Math.round(imgAspect*100)/100} 縦目いっぱい2`)
}
if (a.clientWidth < document.documentElement.clientWidth * 0.48) document.body.appendChild(panel);
// if (verbose) popup(panel.src);
return panel.src;
}
function elegeta(xpath, node = document) {
var array = [];
var ele = document.evaluate("." + xpath, node, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
for (var i = 0; i < ele.snapshotLength; i++) array[i] = ele.snapshotItem(i);
return array;
}
function pref(name, store = null) { // prefs(name,data)で書き込み(数値でも文字列でも配列でもオブジェクトでも可)、prefs(name)で読み出し
if (store === null) { // 読み出し
let data = GM_getValue(name) || GM_getValue(name);
if (data == undefined) return null; // 値がない
if (data.substring(0, 1) === "[" && data.substring(data.length - 1) === "]") { // 配列なのでJSONで返す
try { return JSON.parse(data || '[]'); } catch (e) {
alert("データベースがバグってるのでクリアします\n" + e);
pref(name, []);
return;
}
} else return data;
}
if (store === "" || store === []) { // 書き込み、削除
GM_deleteValue(name);
return;
} else if (typeof store === "string") { // 書き込み、文字列
GM_setValue(name, store);
return store;
} else { // 書き込み、配列
try { GM_setValue(name, JSON.stringify(store)); } catch (e) {
alert("データベースがバグってるのでクリアします\n" + e);
pref(name, "");
}
return store;
}
}
function prefRestrict(name, type) {
let data = pref(name);
if (!data) return data;
if (type === "array") {
if (typeof data === "object") {
return data;
} else {
alert("データベースの形式に誤りがある(配列/オブジェクトでない)ためクリアします\n");
pref(name, "");
return [];
}
}
return data;
}
function proInput(prom, defaultval, min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER) {
let ret = window.prompt(prom, defaultval)
if (ret === null) return null;
if (ret === "") return "";
return Math.min(Math.max(Number(ret.replace(/[A-Za-z0-9.-]/g, function(s) { return String.fromCharCode(s.charCodeAt(0) - 65248); }).replace(/[^-^0-9^\.]/g, "")), min), max);
}
function popup(text, color = "#6080ff") {
text = String(text);
var e = document.getElementById("hzbox");
if (e) { e.remove(); }
var e = document.body.appendChild(document.createElement("span"));
e.innerHTML = '/gm, "\\n") + '\"; document.body.appendChild(a); a.select(); document.execCommand(\"copy\"); a.parentElement.removeChild(a);\'">' + text + '';
setTimeout((function(e) { return function() { e.remove(); } })(e), 5000);
}
})();