您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Spins album artwork with grooves, shine, spindle, and a turntable-style base behind the record. Syncs with play/pause state on YouTube Music.
// ==UserScript== // @name YouTube Music Spinning Album Art (Turntable Base) // @namespace http://tampermonkey.net/ // @version 2.4 // @description Spins album artwork with grooves, shine, spindle, and a turntable-style base behind the record. Syncs with play/pause state on YouTube Music. // @author Chesley // @match https://music.youtube.com/* // @license MIT // @grant none // @run-at document-idle // ==/UserScript== (() => { 'use strict'; const STYLE_ID = 'spinning-cropped-art-style'; let initialized = false; let lastImgKey = null; let songObserver = null; let playListenersBound = false; function addStyles() { if (document.getElementById(STYLE_ID)) return; const style = document.createElement('style'); style.id = STYLE_ID; style.textContent = ` :root { /* Animation */ --album-spin-duration: 8s; --shine-duration: 6s; /* Turntable base sizing (relative to player area) */ --base-size: 115%; /* Decreased from 120% to 115% */ /* Thumbnail ring & effects */ --thumb-ring: rgba(255, 255, 255, 0.2); --thumb-glow: rgba(255, 255, 255, 0.2); } @keyframes spinAlbum { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } @keyframes shineMove { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } /* Respect reduced motion */ @media (prefers-reduced-motion: reduce) { ytmusic-player #thumbnail img, ytmusic-player #thumbnail .shine { animation: none !important; } } /* --- Player container & Turntable Base --- */ ytmusic-player { position: relative; /* needed for ::after positioning */ background: transparent; contain: layout paint style; } /* The "turntable" disk under the record */ ytmusic-player::after { content: ""; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: var(--base-size); height: var(--base-size); background: radial-gradient(circle, var(--base-inner-color) 40%, var(--base-outer-color) 100%); border-radius: 50%; z-index: 0; /* sits behind the record (#thumbnail) */ pointer-events: none; filter: drop-shadow(0 20px 40px rgba(0,0,0,0.35)); } /* --- Record (thumbnail) --- */ ytmusic-player #thumbnail { display: flex; align-items: center; justify-content: center; overflow: hidden; border-radius: 50%; width: 85%; /* Decreased from 90% to 85% */ height: 85%; /* Decreased from 90% to 85% */ margin: auto; position: relative; border: 2px solid var(--thumb-ring); box-shadow: 0 0 20px var(--thumb-glow); will-change: transform; backface-visibility: hidden; z-index: 1; /* above the base (::after) */ } ytmusic-player #thumbnail img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; animation: spinAlbum var(--album-spin-duration) linear infinite; animation-play-state: running; box-shadow: 0 0 15px rgba(255, 255, 255, 0.5); z-index: 2; will-change: transform; backface-visibility: hidden; } ytmusic-player #thumbnail.paused img, ytmusic-player #thumbnail.paused .shine { animation-play-state: paused; } /* Grooves overlay */ ytmusic-player #thumbnail .grooves { position: absolute; inset: 0; border-radius: 50%; background: repeating-radial-gradient( circle, rgba(255,255,255,0.08) 0px, rgba(255,255,255,0.08) 14.8px, rgba(0,0,0,0.28) 15.3px, rgba(0,0,0,0) 18px ); pointer-events: none; z-index: 3; mix-blend-mode: soft-light; } /* Smoother feathered shine (reduced white edge) */ ytmusic-player #thumbnail .shine { position: absolute; inset: 0; border-radius: 50%; background: conic-gradient( from 0deg, rgba(255,255,255,0.18) 0deg, rgba(255,255,255,0.12) 30deg, rgba(255,255,255,0.05) 80deg, rgba(255,255,255,0.00) 140deg, rgba(255,255,255,0.00) 360deg ); animation: shineMove var(--shine-duration) linear infinite; pointer-events: none; z-index: 4; will-change: transform; } /* Spindle / center hub */ ytmusic-player #thumbnail::after { content: ""; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 28px; height: 28px; background: radial-gradient(circle at 45% 45%, #555 0%, #333 40%, #000 100%); border-radius: 50%; z-index: 5; pointer-events: none; } `; document.head.appendChild(style); } const getThumbnail = () => document.querySelector('ytmusic-player #thumbnail'); const getImg = () => getThumbnail()?.querySelector('img') || null; const getVideo = () => document.querySelector('video'); function applyDecorations() { const thumbnail = getThumbnail(); const img = getImg(); if (!thumbnail || !img) return; // Change key if src or srcset changes const key = [img.src || '', img.srcset || ''].join('|'); if (key && key === lastImgKey) return; lastImgKey = key; if (!thumbnail.querySelector('.grooves')) { const grooves = document.createElement('div'); grooves.className = 'grooves'; thumbnail.appendChild(grooves); } if (!thumbnail.querySelector('.shine')) { const shine = document.createElement('div'); shine.className = 'shine'; thumbnail.appendChild(shine); } } function updatePausedState() { const video = getVideo(); const thumbnail = getThumbnail(); if (!thumbnail) return; const shouldPause = !video || video.paused || document.hidden; thumbnail.classList.toggle('paused', !!shouldPause); } function bindPlaybackSync() { if (playListenersBound) return; const video = getVideo(); if (!video) return; const onPlay = () => updatePausedState(); const onPause = () => updatePausedState(); video.addEventListener('play', onPlay); video.addEventListener('pause', onPause); document.addEventListener('visibilitychange', onPause); playListenersBound = true; updatePausedState(); } function observeSongChanges() { if (songObserver) return; const player = document.querySelector('ytmusic-player'); if (!player) return; songObserver = new MutationObserver((mutations) => { let relevant = false; for (const m of mutations) { if (m.type === 'attributes' && (m.attributeName === 'src' || m.attributeName === 'srcset')) { relevant = true; break; } if (m.type === 'childList') { relevant = true; break; } } if (relevant) { applyDecorations(); bindPlaybackSync(); } }); songObserver.observe(player, { childList: true, subtree: true, attributes: true, attributeFilter: ['src', 'srcset'] }); } function initialize() { if (initialized) return; const img = getImg(); if (!img) return; addStyles(); applyDecorations(); bindPlaybackSync(); observeSongChanges(); initialized = true; } // Bootstrap: wait until album <img> exists; guard prevents double init const bootstrap = new MutationObserver(() => { if (getImg()) { initialize(); // keep observing; SPA route changes can nuke nodes } }); bootstrap.observe(document.documentElement, { childList: true, subtree: true }); // Reset cache between tracks/routes window.addEventListener('yt-page-data-updated', () => { lastImgKey = null; applyDecorations(); }); })();