您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
A user script to add a little functionality related to YouTube thumbnails.
- // ==UserScript==
- // @name YouTube - Thumbnail Anywhere
- // @name:ja YouTube - サムネイルといっしょ
- // @description A user script to add a little functionality related to YouTube thumbnails.
- // @description:ja YouTubeのサムネイルに関するちょっとした機能を追加するユーザースクリプトです。
- // @version 1.0.1
- // @icon https://www.google.com/s2/favicons?sz=64&domain=www.youtube.com
- // @match https://www.youtube.com/*
- // @namespace https://github.com/sqrtox/yt-thumbnail-anywhere
- // @author sqrtox
- // @license MIT
- // @grant none
- // @runAt document-end
- // ==/UserScript==
- // NOTE: This file was built by esbuild
- (async () => {
- "use strict";
- // src/css.ts
- var CSS_CLASS_PREFIX = "userscript-";
- var createCssClasses = (...classes) => {
- const result = {};
- for (const class_ of classes) {
- result[class_] =
- `${CSS_CLASS_PREFIX}${Math.random().toString(36).slice(2)}`;
- }
- return result;
- };
- var injectCss = (css) => {
- const style = document.createElement("style");
- style.setHTMLUnsafe(css);
- document.head.append(style);
- };
- // src/image.ts
- var loadImage = (src) => {
- const { promise, resolve, reject } = Promise.withResolvers();
- const events = new AbortController();
- const image = document.createElement("img");
- image.addEventListener(
- "load",
- () => {
- events.abort();
- resolve(image);
- },
- {
- signal: events.signal,
- },
- );
- image.addEventListener(
- "error",
- () => {
- events.abort();
- reject();
- },
- {
- signal: events.signal,
- },
- );
- image.src = src;
- return promise;
- };
- // src/thumbnail.ts
- var applySmallThumbnail = async (watchId, largeThumbnail2) => {
- const classes = createCssClasses("title", "smallThumbnail");
- injectCss(`
- .${classes.title} {
- display: flex;
- align-items: center;
- column-gap: 1rem;
- }
- .${classes.smallThumbnail} {
- height: 64px;
- }
- .${classes.smallThumbnail}:hover {
- position: relative;
- }
- .${classes.smallThumbnail}:hover::after {
- position: absolute;
- cursor: zoom-in;
- top: 0;
- left: 0;
- content: "";
- backdrop-filter: brightness(0.75);
- width: 100%;
- height: 100%;
- }
- .${classes.smallThumbnail} img {
- height: 100%;
- width: auto;
- }
- `);
- const title = document.querySelector("#title:has(> h1)");
- if (!title) {
- return;
- }
- title.classList.add(classes.title);
- const events = new AbortController();
- const smallThumbnail = document.createElement("div");
- smallThumbnail.classList.add(classes.smallThumbnail);
- smallThumbnail.addEventListener(
- "click",
- async () => {
- await largeThumbnail2.expand(watchId);
- },
- {
- signal: events.signal,
- },
- );
- title.insertBefore(smallThumbnail, title.firstChild);
- const image = await loadImage(
- `https://i.ytimg.com/vi/${encodeURIComponent(watchId)}/default.jpg`,
- );
- smallThumbnail.append(image);
- const dispose = () => {
- events.abort();
- smallThumbnail.remove();
- title.classList.remove(classes.title);
- };
- document.addEventListener(
- "yt-navigate-finish",
- () => {
- dispose();
- },
- {
- signal: events.signal,
- },
- );
- };
- var createLargeThumbnail = () => {
- const classes = createCssClasses("hidden", "largeThumbnail");
- injectCss(`
- .${classes.largeThumbnail} {
- display: flex;
- align-items: center;
- justify-content: center;
- position: fixed;
- width: 100svw;
- height: 100svh;
- top: 0;
- left: 0;
- cursor: zoom-out;
- backdrop-filter: brightness(0.5);
- z-index: 9999999;
- }
- .${classes.largeThumbnail} img {
- max-width: 50%;
- max-height: 50%;
- width: auto;
- height: auto;
- vertical-align: middle;
- }
- .${classes.hidden} {
- display: none;
- }
- `);
- const largeThumbnail2 = document.createElement("div");
- largeThumbnail2.classList.add(classes.hidden, classes.largeThumbnail);
- const handleClick = () => {
- controller.shrink();
- };
- const controller = {
- expand: async (watchId) => {
- const image = await loadImage(
- `https://i.ytimg.com/vi/${encodeURIComponent(watchId)}/maxresdefault.jpg`,
- );
- document.addEventListener("click", handleClick);
- largeThumbnail2.replaceChildren(image);
- largeThumbnail2.classList.remove(classes.hidden);
- },
- shrink: () => {
- document.removeEventListener("click", handleClick);
- largeThumbnail2.classList.add(classes.hidden);
- },
- };
- document.addEventListener("yt-navigate-start", () => {
- controller.shrink();
- });
- document.body.append(largeThumbnail2);
- return controller;
- };
- // src/index.ts
- var largeThumbnail = createLargeThumbnail();
- var apply = async () => {
- let watchId;
- if (location.pathname.startsWith("/live/")) {
- watchId = location.pathname.split("/").pop();
- } else {
- const searchParams = new URLSearchParams(location.search);
- watchId = searchParams.get("v") ?? void 0;
- }
- if (!watchId) {
- return;
- }
- await applySmallThumbnail(watchId, largeThumbnail);
- };
- document.addEventListener("yt-page-data-updated", async (event) => {
- if (event.detail.pageType !== "watch") {
- return;
- }
- await apply();
- });
- await apply();
- })();