Sleazy Fork is available in English.
Обложки видео активны в зоне видимости с авто-восстановлением
// ==UserScript==
// @name R34 Thumbnail always active
// @namespace http://tampermonkey.net/
// @version 3.32
// @description Обложки видео активны в зоне видимости с авто-восстановлением
// @author Gemini
// @match https://rule34video.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=rule34video.com
// @grant none
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
const ITEM_SELECTOR = '.item.thumb, .item.ma-v';
const WRAP_SELECTOR = '.img.wrap_image, .ma-thumb';
const visibleItems = new Set();
const pendingRoots = new Set();
let processRafId = null;
// Внедрение стилей разметки
const style = document.createElement('style');
style.textContent = `
.wrap_image,
.ma-thumb {
position: relative;
}
.wrap_image[data-has-trailer="1"] > img,
.ma-thumb[data-has-trailer="1"] > img {
display: none !important;
}
.wrap_image video[our-trailer],
.ma-thumb video[our-trailer] {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
z-index: 1;
pointer-events: none;
background: #000;
}
.wrap_image > :not(img):not(video),
.ma-thumb > :not(img):not(video),
.ma-thumb .ma-dur,
.ma-thumb .ma-hd {
position: absolute;
z-index: 5;
}
`;
document.head.appendChild(style);
// Восстановление зависшего видео (только если оно в зоне видимости)
function recoverVideo(video, item) {
if (!video.isConnected || !visibleItems.has(item) || document.hidden) return;
if (video.dataset.recovering === '1') return;
video.dataset.recovering = '1';
setTimeout(() => {
if (video.isConnected && visibleItems.has(item) && !document.hidden) {
if (video.readyState < 2 || video.error) {
video.load();
video.play().catch(() => {});
}
}
video.dataset.recovering = '0';
}, 1200);
}
// Инициализация наблюдателя видимости
const visibilityObserver = new IntersectionObserver((entries) => {
for (let i = 0; i < entries.length; i++) {
const entry = entries[i];
const item = entry.target;
if (entry.isIntersecting) {
visibleItems.add(item);
activateTrailer(item);
const video = item.querySelector('video[our-trailer]');
if (video && !document.hidden) {
video.play().catch(() => {});
}
} else {
visibleItems.delete(item);
const video = item.querySelector('video[our-trailer]');
if (video) {
video.pause();
}
}
}
}, { rootMargin: '100px 0px 100px 0px', threshold: 0.05 });
// Монтирование видео
function activateTrailer(item) {
const wrap = item.querySelector(WRAP_SELECTOR);
const img = wrap?.querySelector('img.thumb, img');
const previewUrl = wrap?.getAttribute('data-preview');
if (!wrap || !img || !previewUrl) return;
item.dataset.trailerHoverBlocked = '1';
const existingVideos = wrap.querySelectorAll('video');
for (let i = 0; i < existingVideos.length; i++) {
const v = existingVideos[i];
if (!v.hasAttribute('our-trailer')) v.remove();
}
let video = wrap.querySelector('video[our-trailer]');
if (video) {
wrap.dataset.hasTrailer = '1';
img.style.display = 'none';
video.style.display = 'block';
return;
}
// Создание элемента видео
video = document.createElement('video');
video.setAttribute('our-trailer', '1');
video.dataset.previewUrl = previewUrl;
video.src = previewUrl;
video.loop = true;
video.muted = true;
video.playsInline = true;
video.preload = 'metadata';
// Нативные события зависания сети без глобальных таймеров
video.addEventListener('stalled', () => recoverVideo(video, item));
video.addEventListener('error', () => recoverVideo(video, item));
wrap.dataset.hasTrailer = '1';
img.style.display = 'none';
if (wrap.contains(img)) {
wrap.insertBefore(video, img);
} else {
wrap.prepend(video);
}
}
// Регистрация карточки
function processItem(item) {
if (!(item instanceof HTMLElement) || !item.matches(ITEM_SELECTOR)) return;
if (item.dataset.trailerObserved === '1') return;
item.dataset.trailerObserved = '1';
visibilityObserver.observe(item);
}
// Обработка контейнеров
function processRoot(root) {
if (!root) return;
if (root instanceof Element && root.matches(ITEM_SELECTOR)) {
processItem(root);
}
if (root.querySelectorAll) {
const items = root.querySelectorAll(ITEM_SELECTOR);
for (let i = 0; i < items.length; i++) {
processItem(items[i]);
}
}
}
// Очистка очереди обработки
function flushPendingRoots() {
const roots = Array.from(pendingRoots);
pendingRoots.clear();
for (let i = 0; i < roots.length; i++) {
processRoot(roots[i]);
}
}
// Планирование кадра обработки
function scheduleProcess(root = document) {
pendingRoots.add(root);
if (processRafId) return;
processRafId = requestAnimationFrame(() => {
processRafId = null;
flushPendingRoots();
});
}
// Управление видимостью документа
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
visibleItems.forEach((item) => {
const video = item.querySelector('video[our-trailer]');
if (video) video.pause();
});
} else {
visibleItems.forEach((item) => {
const video = item.querySelector('video[our-trailer]');
if (video) {
if (video.readyState < 2) {
video.load();
}
video.play().catch(() => {});
}
});
}
});
// Блокировка событий наведения
['mouseenter', 'mouseover', 'mouseleave', 'mouseout'].forEach((eventName) => {
document.addEventListener(eventName, (event) => {
const target = event.target;
if (target && target !== document && target.closest?.('[data-trailer-hover-blocked="1"]')) {
if (eventName === 'mouseleave' || eventName === 'mouseout') {
event.stopImmediatePropagation();
return;
}
if (!target.closest?.('.vv-cardchip, [data-vv-chip]')) {
event.stopImmediatePropagation();
}
}
}, true);
});
// Отслеживание мутаций DOM
new MutationObserver((mutations) => {
for (let i = 0; i < mutations.length; i++) {
const mutation = mutations[i];
for (let j = 0; j < mutation.addedNodes.length; j++) {
const node = mutation.addedNodes[j];
if (node.nodeType === 1) {
if (node.matches?.('.vv-pick, [role="dialog"], .fs-pop, .fs-sheet') ||
node.closest?.('.vv-pick, [role="dialog"], .fs-pop, .fs-sheet')) {
continue;
}
scheduleProcess(node);
}
}
}
}).observe(document.body, { childList: true, subtree: true });
// Обработка отложенной загрузки
document.addEventListener('lazyloaded', () => {
scheduleProcess(document);
}, true);
// Запуск скрипта
scheduleProcess(document);
})();