Scrollable Volume Control with Custom Collapse and Step Control

10/10/24

Tính đến 19-10-2024. Xem phiên bản mới nhất.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

Bạn sẽ cần cài đặt một tiện ích mở rộng như Tampermonkey hoặc Violentmonkey để cài đặt kịch bản này.

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

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

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

You will need to install a user script manager extension to install this script.

(Tôi đã có Trình quản lý tập lệnh người dùng, hãy cài đặt nó!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        Scrollable Volume Control with Custom Collapse and Step Control
// @namespace   Tampermonkey Scripts
// @match       https://www.redgifs.com/*
// @grant       GM_registerMenuCommand
// @version     2.8
// @author      Bunglover24
// @license     GNU GPLv3
// @icon        https://icons.duckduckgo.com/ip2/redgifs.com.ico
// @description 10/10/24
// ==/UserScript==

const isFirefox = typeof InstallTrigger !== 'undefined'; // Detect Firefox

// Default step size for volume control, saved in localStorage
let volumeStep = parseFloat(localStorage.getItem('volumeStep')) || 0.01;

// Helper to save square position to localStorage
function savePosition(x, y) {
  localStorage.setItem('volumeControlPos', JSON.stringify({ x, y }));
}

// Helper to load saved square position
function loadPosition() {
  const pos = JSON.parse(localStorage.getItem('volumeControlPos'));
  return pos ? { x: pos.x, y: pos.y } : { x: window.innerWidth / 2, y: window.innerHeight / 2 };
}

// Create the volume control square
function createVolumeControlSquare(video) {
  const controlSquare = document.createElement('div');
  const { x, y } = loadPosition();

  // Style for the square
  Object.assign(controlSquare.style, {
    position: 'fixed',
    left: `${x}px`,
    top: `${y}px`,
    width: '8.5mm', // Size adjusted for 100% text
    height: '8.5mm', // Size adjusted for 100% text
    backgroundColor: 'rgba(23, 23, 23, 0.8)', // Slight transparency for the square
    color: 'white',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    borderRadius: '3px', // Slight corner rounding
    cursor: 'pointer',
    zIndex: 1000,
    transition: 'all 0.3s ease',
  });

  let volume = localStorage.getItem('videoVolume') || 0.01; // Default to 1%
  video.volume = volume;

  // Volume text, centered and styled
  const volumeText = document.createElement('div');
  volumeText.textContent = `${Math.round(volume * 100)}%`;
  Object.assign(volumeText.style, {
    pointerEvents: 'none', // Prevent interaction
    fontSize: '12px',
    fontFamily: 'Tahoma, sans-serif',
    color: 'rgba(255, 255, 255, 0.9)', // Slightly less transparent text
    textShadow: '1px 1px 3px rgba(0, 0, 0, 0.5)', // Subtle shadow for contrast
    zIndex: 2, // Ensure it's above other elements
  });
  controlSquare.appendChild(volumeText);

  // Volume control via scroll, adjustable steps
  controlSquare.addEventListener('wheel', (e) => {
    e.preventDefault();
    volume = Math.min(Math.max(parseFloat(volume) + (e.deltaY > 0 ? -volumeStep : volumeStep), 0), 1);
    video.volume = volume;
    localStorage.setItem('videoVolume', volume);
    volumeText.textContent = `${Math.round(volume * 100)}%`;

    if (isFirefox) {
      video.muted = false; // Firefox-specific handling
    }
  });

  // Create the drag bar, adjusted to 3 pixels tall
  const dragBar = document.createElement('div');
  Object.assign(dragBar.style, {
    position: 'absolute',
    top: '0px', // Positioned at the top
    width: '100%',
    height: '3px', // Adjusted height to 3 pixels
    backgroundColor: 'rgba(150, 200, 60, 0.9)', // Dragbar color (#96C83C)
    cursor: 'move',
    zIndex: 1, // Keep it behind the volume text
  });
  controlSquare.appendChild(dragBar);

  // Optimized drag functionality
  let isDragging = false, offsetX = 0, offsetY = 0;
  dragBar.addEventListener('mousedown', (e) => {
    isDragging = true;
    offsetX = e.clientX - controlSquare.getBoundingClientRect().left;
    offsetY = e.clientY - controlSquare.getBoundingClientRect().top;
    document.body.style.userSelect = 'none'; // Disable text selection while dragging
  });

  document.addEventListener('mousemove', (e) => {
    if (isDragging) {
      requestAnimationFrame(() => {
        const x = Math.max(0, Math.min(e.clientX - offsetX, window.innerWidth - controlSquare.offsetWidth));
        const y = Math.max(0, Math.min(e.clientY - offsetY, window.innerHeight - controlSquare.offsetHeight));
        controlSquare.style.left = `${x}px`;
        controlSquare.style.top = `${y}px`;
      });
    }
  });

  document.addEventListener('mouseup', () => {
    if (isDragging) {
      const x = controlSquare.getBoundingClientRect().left;
      const y = controlSquare.getBoundingClientRect().top;
      savePosition(x, y); // Save position on release
      isDragging = false;
      document.body.style.userSelect = ''; // Re-enable text selection
    }
  });

  // Handle collapse/expand on double-click
  let isCollapsed = false;
  controlSquare.addEventListener('dblclick', (e) => {
    if (e.target !== dragBar) {
      if (!isCollapsed) {
        controlSquare.style.width = '5mm'; // Collapsed size, 7mm larger than original
        controlSquare.style.height = '5mm'; // Collapsed size, 7mm larger than original
        controlSquare.style.backgroundColor = 'rgba(150, 200, 60, 0.9)'; // Match dragbar color (#96C83C)
        volumeText.style.display = 'none'; // Hide volume text
        dragBar.style.display = 'none'; // Hide drag bar
      } else {
        controlSquare.style.width = '8.5mm'; // Restore original size
        controlSquare.style.height = '8.5mm'; // Restore original size
        controlSquare.style.backgroundColor = 'rgba(23, 23, 23, 0.8)'; // Restore transparency
        volumeText.style.display = 'block'; // Show volume text
        dragBar.style.display = 'block'; // Show drag bar
      }
      isCollapsed = !isCollapsed;
    }
  });

  // Click to uncollapse
  controlSquare.addEventListener('click', () => {
    if (isCollapsed) {
      controlSquare.style.width = '8.5mm'; // Restore original size
      controlSquare.style.height = '8.5mm'; // Restore original size
      controlSquare.style.backgroundColor = 'rgba(23, 23, 23, 0.8)'; // Restore transparency
      volumeText.style.display = 'block'; // Show volume text
      dragBar.style.display = 'block'; // Show drag bar
      isCollapsed = false;
    }
  });

  // Add the control square to the page
  document.body.appendChild(controlSquare);
}

// Wait for video element to load using MutationObserver
let observer = new MutationObserver((mutations) => {
  for (let mutation of mutations) {
    if (mutation.addedNodes.length) {
      let element = document.querySelector('.GifPreview_isActive');
      if (element) {
        const video = document.querySelector('video');
        if (video) {
          createVolumeControlSquare(video);
          observer.disconnect(); // Stop observing once square is added
          return;
        }
      }
    }
  }
});

observer.observe(document, { childList: true, subtree: true });

// Register Tampermonkey menu commands

// Reset button to reset volume to 1%
GM_registerMenuCommand('Reset Volume to 1%', () => {
  const videos = document.querySelectorAll('video');
  videos.forEach((video) => {
    video.volume = 0.01;
    localStorage.setItem('videoVolume', 0.01);
  });
  alert('Volume has been reset to 1%.');
});

// Button to set volume step size
GM_registerMenuCommand('Set Volume Step Size', () => {
  let step = prompt(`Current step size is ${volumeStep * 100}%. Enter new step size as a percentage:`, volumeStep * 100);
  if (step !== null) {
    step = parseFloat(step) / 100;
    if (!isNaN(step) && step > 0 && step <= 1) {
      volumeStep = step;
      localStorage.setItem('volumeStep', volumeStep);
      alert(`Volume step size set to ${volumeStep * 100}%`);
    } else {
      alert('Invalid input. Please enter a number between 0.01 and 100.');
    }
  }
});