全屏按钮(适用于移动设备)

一个全屏按钮,支持拖动、自动淡化、防穿透、适配移动端横竖屏变化。

当前为 2025-06-29 提交的版本,查看 最新版本

// ==UserScript==
// @name         全屏按钮(适用于移动设备)
// @name:en      Full screen button (for mobile devices)
// @namespace    http://tampermonkey.net/
// @version      2.1
// @description  一个全屏按钮,支持拖动、自动淡化、防穿透、适配移动端横竖屏变化。
// @description:zh-CN  一个全屏按钮,支持拖动、自动淡化、防穿透、适配移动端横竖屏变化。
// @description:en     A full-screen button, Supports dragging, automatic fading, anti-penetration, and adapts to the horizontal and vertical screen changes of mobile terminals.
// @author       凡留钰 + ChatGPT
// @contributor  Gemini(某位(忘了额)网友用Gemini做过)
// @match        *://*/*
// @icon         https://greasyfork.s3.us-east-2.amazonaws.com/4eb17e88irkc3910fvbpp4f0h270
// @grant        none
// @noframes
// @license      MIT
// ==/UserScript==

(function () {
  'use strict';

  const btn = document.createElement('button');

  let leftPercent = 95;
  let topPercent = 95;

  Object.assign(btn.style, {
    position: 'fixed',
    width: '4vw', // 按钮大小_百分比
    height: '4vw',
    minWidth: '24px', // 按钮大小_最小
    minHeight: '24px',
    maxWidth: '48px', // 按钮大小_最大
    maxHeight: '48px',
    backgroundImage: 'url("https://greasyfork.s3.us-east-2.amazonaws.com/4eb17e88irkc3910fvbpp4f0h270")',
    backgroundSize: 'cover',
    backgroundRepeat: 'no-repeat',
    backgroundPosition: 'center',
    borderRadius: '50%',
    boxShadow: '0 2px 5px rgba(0,0,0,0.3)',
    border: 'none',
    borderRadius: '50%',
    zIndex: '999',
    cursor: 'grab',
    userSelect: 'none',
    transition: 'opacity 0.3s ease',
    opacity: '1',
  });

  document.body.appendChild(btn);

  function updatePosition() {
    const width = btn.offsetWidth;
    const height = btn.offsetHeight;
    const left = (leftPercent / 100) * (window.innerWidth - width);
    const top = (topPercent / 100) * (window.innerHeight - height);
    btn.style.left = `${left}px`;
    btn.style.top = `${top}px`;
  }

  function clampAndUpdatePosition() {
    leftPercent = Math.max(0, Math.min(100, leftPercent));
    topPercent = Math.max(0, Math.min(100, topPercent));
    updatePosition();
  }

  function isFullscreen() {
    return document.fullscreenElement || document.webkitFullscreenElement;
  }

  function toggleFullscreen() {
    const el = document.documentElement;
    if (!isFullscreen()) {
      (el.requestFullscreen || el.webkitRequestFullscreen)?.call(el);
    } else {
      (document.exitFullscreen || document.webkitExitFullscreen)?.call(document);
    }
  }

  let dragging = false, moved = false;
  let offsetX = 0, offsetY = 0, startX = 0, startY = 0;

  function dragStart(e) {
    e.preventDefault();
    dragging = true;
    moved = false;

    const x = e.touches ? e.touches[0].clientX : e.clientX;
    const y = e.touches ? e.touches[0].clientY : e.clientY;
    const rect = btn.getBoundingClientRect();
    offsetX = x - rect.left;
    offsetY = y - rect.top;
    startX = x;
    startY = y;

    btn.style.cursor = 'grabbing';
    clearHideTimer();

    const moveEvt = e.type === 'touchstart' ? 'touchmove' : 'mousemove';
    const endEvt = e.type === 'touchstart' ? 'touchend' : 'mouseup';
    document.addEventListener(moveEvt, dragMove, { passive: false });
    document.addEventListener(endEvt, dragEnd);
  }

  function dragMove(e) {
    if (!dragging) return;
    e.preventDefault();

    const x = e.touches ? e.touches[0].clientX : e.clientX;
    const y = e.touches ? e.touches[0].clientY : e.clientY;

    if (Math.abs(x - startX) > 5 || Math.abs(y - startY) > 5) moved = true;

    const newLeft = x - offsetX;
    const newTop = y - offsetY;

    const width = btn.offsetWidth;
    const height = btn.offsetHeight;

    leftPercent = (newLeft / (window.innerWidth - width)) * 100;
    topPercent = (newTop / (window.innerHeight - height)) * 100;

    clampAndUpdatePosition();
  }

  function dragEnd(e) {
    dragging = false;
    btn.style.cursor = 'grab';

    document.removeEventListener('mousemove', dragMove);
    document.removeEventListener('mouseup', dragEnd);
    document.removeEventListener('touchmove', dragMove);
    document.removeEventListener('touchend', dragEnd);

    if (!moved) {
      e.preventDefault();
      toggleFullscreen();
    }

    startHideTimer();
  }

  btn.addEventListener('mousedown', dragStart);
  btn.addEventListener('touchstart', dragStart, { passive: false });

  btn.addEventListener('click', (e) => {
    e.stopPropagation();
    e.preventDefault();
  });

  let hideTimer;
  function startHideTimer() {
    clearTimeout(hideTimer);
    hideTimer = setTimeout(() => {
      btn.style.opacity = '0.4';
    }, 3000);
  }

  function clearHideTimer() {
    clearTimeout(hideTimer);
    btn.style.opacity = '0.8';
  }

  ['mouseenter', 'touchstart'].forEach(evt => btn.addEventListener(evt, clearHideTimer));
  ['mouseleave', 'touchend'].forEach(evt => btn.addEventListener(evt, startHideTimer));

  window.addEventListener('resize', clampAndUpdatePosition);
  window.addEventListener('orientationchange', clampAndUpdatePosition);

  clampAndUpdatePosition();
  startHideTimer();

})();