Twitter 放大圖片

放大滑鼠游標下的圖片。

  1. // ==UserScript==
  2. // @name Twitter: Zoom In Image
  3. // @name:zh-TW Twitter 放大圖片
  4. // @name:zh-CN Twitter 放大图像
  5. // @name:ja Twitter 画像を拡大
  6. // @name:ko Twitter 이미지 확대
  7. // @name:ru Twitter Увеличить изображение
  8. // @version 1.2.5
  9. // @description Zoom in the image which is under the cursor.
  10. // @description:zh-TW 放大滑鼠游標下的圖片。
  11. // @description:zh-CN 放大滑鼠光标下的图像。
  12. // @description:ja カーソルの下にある画像を拡大します。
  13. // @description:ko 커서 아래에있는 이미지를 확대합니다.
  14. // @description:ru Увеличьте изображение под курсором.
  15. // @author Hayao-Gai
  16. // @namespace https://github.com/HayaoGai
  17. // @icon https://i.imgur.com/M9oO8K9.png
  18. // @match https://twitter.com/*
  19. // @grant none
  20. // ==/UserScript==
  21.  
  22. /* jshint esversion: 6 */
  23.  
  24. (function() {
  25. 'use strict';
  26.  
  27. const svgLoading = `<svg width="100%" height="100%" viewBox="0 0 32 32"><circle cx="16" cy="16" fill="none" r="14" stroke-width="4" style="opacity: 0.2;"></circle><circle cx="16" cy="16" fill="none" r="14" stroke-width="4" style="stroke-dasharray: 80; stroke-dashoffset: 60;"></circle></svg>`;
  28. const textStyle = `
  29. .zoomin-loading {
  30. position: fixed;
  31. width: 26px;
  32. height: 26px;
  33. display: none;
  34. }
  35. .zoomin-loading-show {
  36. display: flex !important;
  37. }
  38. .zoomin-canvas {
  39. border-radius: 8px;
  40. position: fixed;
  41. background-color: #e0e0e0;
  42. pointer-events: none;
  43. opacity: 0;
  44. }
  45. .zoomin-canvas-show {
  46. transition: opacity 0.4s;
  47. opacity: 1 !important;
  48. }
  49. .zoomin-zoom {
  50. border-radius: 8px;
  51. position: fixed;
  52. pointer-events: none;
  53. opacity: 0;
  54. }
  55. .zoomin-zoom-show {
  56. transition: opacity 0.4s;
  57. opacity: 1 !important;
  58. }`;
  59. let currentUrl = document.location.href;
  60. let updating = false, showing = false;
  61. let loading, canvas, zoom, currentImage;
  62.  
  63. css();
  64.  
  65. init(10);
  66.  
  67. locationChange();
  68.  
  69. window.addEventListener("scroll", update);
  70.  
  71. function init(times) {
  72. for (let i = 0; i < times; i++) {
  73. setTimeout(removeBlock, 500 * i);
  74. setTimeout(createLoading, 500 * i);
  75. setTimeout(createCanvas, 500 * i);
  76. setTimeout(createZoom, 500 * i);
  77. setTimeout(eventListener, 500 * i);
  78. setTimeout(sensitiveContent, 500 * i);
  79. }
  80. }
  81.  
  82. // create
  83. function removeBlock() {
  84. // remove the div block on every avatar.
  85. document.querySelectorAll(".r-1twgtwe").forEach(block => block.remove());
  86. }
  87.  
  88. function removeLoading() {
  89. if (!loading) return;
  90. loading.remove();
  91. loading = null;
  92. }
  93.  
  94. function createLoading() {
  95. // check svg
  96. if (!getColor()) return;
  97. // check exist
  98. if (!!loading) return;
  99. // create
  100. loading = document.createElement("div");
  101. loading.className = "zoomin-loading css-1dbjc4n r-17bb2tj r-1muvv40 r-127358a r-1ldzwu0";
  102. loading.innerHTML = svgLoading;
  103. loading.querySelectorAll("circle").forEach(circle => { circle.style.stroke = getColor(); });
  104. document.body.appendChild(loading);
  105. }
  106.  
  107. function createCanvas() {
  108. // check exist
  109. if (!!canvas) return;
  110. // create
  111. canvas = document.createElement("div");
  112. canvas.classList.add("zoomin-canvas");
  113. document.body.appendChild(canvas);
  114. }
  115.  
  116. function createZoom() {
  117. // check exist
  118. if (!!zoom) return;
  119. // create
  120. zoom = document.createElement("img");
  121. zoom.classList.add("zoomin-zoom");
  122. document.body.appendChild(zoom);
  123. }
  124.  
  125. // event
  126. function eventListener() {
  127. // situation 1: disable if you go into the image page.
  128. if (currentUrl.includes("photo/1")) return;
  129. // situation 2: return if loading, canvas or zoom doesn't exist.
  130. if (!loading || !canvas || !zoom) return;
  131. // add thumbnail mouse event.
  132. document.querySelectorAll(".r-4gszlv:not(zoomin-listener)").forEach(thumbnail => {
  133. thumbnail.classList.add("zoomin-listener");
  134. // return if the video thumbnail is exist.
  135. if (!thumbnail.closest(".r-1777fci") || (thumbnail.closest(".r-1777fci") && !thumbnail.closest(".r-1777fci").querySelector("[data-testid='playButton']"))) {
  136. const image = thumbnail.parentElement.querySelector("img");
  137. image.addEventListener("mousemove", showImage);
  138. image.addEventListener("mouseleave", hideImage);
  139. }
  140. });
  141. }
  142.  
  143. function sensitiveContent() {
  144. document.querySelectorAll(".r-42olwf.r-1vsu8ta:not(.zoomin-view)").forEach(view => {
  145. view.classList.add("zoomin-view");
  146. view.addEventListener("click", () => init(3));
  147. });
  148. }
  149.  
  150. function showImage() {
  151. // avoid calling this function multiple times.
  152. if (showing) return;
  153. showing = true;
  154. currentImage = this;
  155. // get image original size url.
  156. const origin = getOrigin(currentImage.src);
  157. if (!origin) return;
  158. zoom.setAttribute("src", origin);
  159. // show loading icon.
  160. loading.style.left = getLeft();
  161. loading.style.top = getTop();
  162. loading.classList.add("zoomin-loading-show");
  163. // detail
  164. zoomDetail();
  165. }
  166.  
  167. function hideImage() {
  168. showing = false;
  169. loading.classList.remove("zoomin-loading-show");
  170. canvas.classList.remove("zoomin-canvas-show");
  171. zoom.classList.remove("zoomin-zoom-show");
  172. zoom.removeAttribute("src");
  173. }
  174.  
  175. function zoomDetail() {
  176. // wait until get the image size.
  177. if (!zoom.naturalWidth)
  178. {
  179. setTimeout(zoomDetail, 100);
  180. return;
  181. }
  182. // hide loading icon.
  183. loading.classList.remove("zoomin-loading-show");
  184. // fit zoom original size for browser.
  185. const w = zoom.naturalWidth;
  186. const h = zoom.naturalHeight;
  187. const clientW = document.documentElement.clientWidth;
  188. const clientH = document.documentElement.clientHeight;
  189. const situation1 = w > clientW;
  190. const situation2 = h > clientH;
  191. if (situation1 && situation2) {
  192. const rate = clientH / h;
  193. const new_w = w * rate;
  194. const new_h = clientH;
  195. if (new_w > clientW) {
  196. const rate2 = clientW / new_w;
  197. const new_w2 = clientW;
  198. const new_h2 = new_h * rate2;
  199. setSize(canvas, new_w2, new_h2);
  200. setSize(zoom, new_w2 - 10, new_h2 - 10);
  201. } else {
  202. setSize(canvas, new_w, new_h);
  203. setSize(zoom, new_w - 10, new_h - 10);
  204. }
  205. } else if (situation1) {
  206. const rate3 = clientW / w;
  207. const new_h3 = h * rate3;
  208. setSize(canvas, clientW, new_h3);
  209. setSize(zoom, clientW - 10, new_h3 - 10);
  210. } else if (situation2) {
  211. const rate4 = clientH / h;
  212. const new_w4 = w * rate4;
  213. setSize(canvas, new_w4, clientH);
  214. setSize(zoom, new_w4 - 10, clientH - 10);
  215. } else {
  216. setSize(canvas, w + 10, h + 10);
  217. setSize(zoom, w, h);
  218. }
  219. // position
  220. const cWidth = parseInt(canvas.style.width);
  221. const cHeight = parseInt(canvas.style.height);
  222. const zWidth = parseInt(zoom.style.width);
  223. const zHeight = parseInt(zoom.style.height);
  224. let cLeft = clientW / 2 - cWidth / 2;
  225. let cTop = clientH / 2 - cHeight / 2;
  226. if (cLeft < 0) cLeft = 0;
  227. if (cTop < 0) cTop = 0;
  228. let zLeft = clientW / 2 - zWidth / 2;
  229. let zTop = clientH / 2 - zHeight / 2;
  230. if (zLeft < 0) zLeft = 0;
  231. if (zTop < 0) zTop = 0;
  232. canvas.classList.add("zoomin-canvas-show");
  233. canvas.style.left = `${cLeft}px`;
  234. canvas.style.top = `${cTop}px`;
  235. zoom.classList.add("zoomin-zoom-show");
  236. zoom.style.left = `${zLeft}px`;
  237. zoom.style.top = `${zTop}px`;
  238. }
  239.  
  240. // method
  241. function getColor() {
  242. let color = "";
  243. document.querySelectorAll("svg.r-50lct3").forEach(svg => {
  244. if (!!color) return;
  245. color = getComputedStyle(svg).color;
  246. });
  247. return color;
  248. }
  249.  
  250. function getOrigin(url) {
  251. // situation 1: post
  252. if (url.includes("media") || url.includes("card")) {
  253. const search = url.split("&name=");
  254. const last = search[search.length - 1];
  255. return url.replace(last, "orig");
  256. }
  257. // situation 2: banner
  258. else if (url.includes("banner")) {
  259. const search = url.split("/");
  260. const last = search[search.length - 1];
  261. return url.replace(last, "1500x500");
  262. }
  263. // situation 3: avatar
  264. else if (url.includes("profile")) {
  265. const search1 = url.split("_");
  266. const search2 = url.split(".");
  267. const last1 = search1[search1.length - 1];
  268. const last2 = search2[search2.length - 1];
  269. return url.replace(`_${last1}`, `.${last2}`);
  270. }
  271. // situation 3: video
  272. else {
  273. return null;
  274. }
  275. }
  276.  
  277. function getLeft() {
  278. return `${document.documentElement.clientWidth / 2 - 13}px`;
  279. }
  280.  
  281. function getTop() {
  282. return `${document.documentElement.clientHeight / 2 - 13}px`;
  283. }
  284.  
  285. function setSize(element, width, height) {
  286. element.style.width = `${width}px`;
  287. element.style.height = `${height}px`;
  288. }
  289.  
  290. // others
  291. function css() {
  292. const style = document.createElement("style");
  293. style.type = "text/css";
  294. style.innerHTML = textStyle;
  295. document.head.appendChild(style);
  296. }
  297.  
  298. function update() {
  299. if (updating) return;
  300. updating = true;
  301. init(3);
  302. setTimeout(() => { updating = false; }, 1000);
  303. }
  304.  
  305. function locationChange() {
  306. const observer = new MutationObserver(mutations => {
  307. mutations.forEach(() => {
  308. if (currentUrl !== document.location.href) {
  309. currentUrl = document.location.href;
  310. hideImage();
  311. removeLoading();
  312. init(10);
  313. }
  314. });
  315. });
  316. const target = document.body;
  317. const config = { childList: true, subtree: true };
  318. observer.observe(target, config);
  319. }
  320.  
  321. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址