YT Scroll Rewind

Mouse wheel rewind over YouTube video

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         YT Scroll Rewind
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Mouse wheel rewind over YouTube video
// @author       You
// @match        https://www.youtube.com/watch*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    // === Config / Настройки ===
    const config = {
        seekTime: 5,        // Seconds to seek per wheel step / Секунд за один шаг колеса
        deadZonePercent: 20 // Ignore edges (%) / Игнорировать края плеера (%)
    };

    // YouTube player container / Контейнер плеера YouTube
    function getPlayer() {
        return document.querySelector('.html5-video-player');
    }

    // HTML5 <video> element / Сам элемент видео
    function getVideo() {
        return document.querySelector('video');
    }

    // Check cursor inside player rect / Курсор внутри плеера
    function isInside(e, rect) {
        return (
            e.clientX >= rect.left && e.clientX <= rect.right &&
            e.clientY >= rect.top  && e.clientY <= rect.bottom
        );
    }

    // Dead zone near edges (avoid conflicts with UI) /
    // Мёртвая зона по краям (чтобы не мешать интерфейсу)
    function isInDeadZone(e, rect) {
        const dz = config.deadZonePercent / 100;
        const x = e.clientX - rect.left;
        const y = e.clientY - rect.top;

        return (
            x < rect.width * dz ||
            x > rect.width * (1 - dz) ||
            y < rect.height * dz ||
            y > rect.height * (1 - dz)
        );
    }

    // Main wheel handler / Основная логика прокрутки
    function handleWheel(e) {
        const player = getPlayer();
        const video = getVideo();
        if (!player || !video) return;

        const rect = player.getBoundingClientRect();
        if (!isInside(e, rect)) return;
        if (isInDeadZone(e, rect)) return;

        // Block YouTube default scroll behavior /
        // Блокируем стандартное поведение YouTube
        e.preventDefault();
        e.stopImmediatePropagation();

        // Scroll up = back, down = forward /
        // Вверх — назад, вниз — вперёд
        video.currentTime += e.deltaY < 0 ? -config.seekTime : config.seekTime;

        // Clamp to video duration /
        // Ограничение в пределах видео
        video.currentTime = Math.max(0, Math.min(video.duration, video.currentTime));
    }

    // Capture phase + passive:false is REQUIRED /
    // Захват + passive:false ОБЯЗАТЕЛЬНЫ
    document.addEventListener('wheel', handleWheel, {
        passive: false,
        capture: true
    });

    // Optional external access to config /
    // Необязательный доступ к настройкам извне
    window.ytScrollConfig = config;
})();