YouTube: Floating Chat Window on Fullscreen

To make floating chat window on fullscreen

目前為 2023-07-18 提交的版本,檢視 最新版本

// ==UserScript==
// @name        YouTube: Floating Chat Window on Fullscreen
// @namespace   UserScript
// @match       https://www.youtube.com/*
// @exclude             /^https?://\S+\.(txt|png|jpg|jpeg|gif|xml|svg|manifest|log|ini)[^\/]*$/
// @version     0.1.4
// @license             MIT License
// @author              CY Fung
// @description To make floating chat window on fullscreen
// @require             https://gf.qytechs.cn/scripts/465819-api-for-customelements-in-youtube/code/API%20for%20CustomElements%20in%20YouTube.js?version=1215161
// @run-at              document-start
// @grant               none
// @unwrap
// @allFrames           true
// @inject-into         page
// ==/UserScript==


((__CONTEXT__) => {

  const createStyleText = () => `


:fullscreen ytd-live-chat-frame#chat {
  position:fixed !important;
    top: var(--f3-top, 5px) !important;
    left: var(--f3-left, calc(60vw + 100px)) !important;
    height: var(--f3-h, 60vh) !important;
    width: var(--f3-w, 320px) !important;
    display:flex !important;
    flex-direction: column !important;
    padding: 4px;
    cursor: all-scroll;
    z-index:9999;
    box-sizing: border-box !important;
    margin:0 !important;
    opacity: var(--floating-window-opacity, 1.0) !important;
}

.no-floating:fullscreen ytd-live-chat-frame#chat {

    top: -300vh !important;
    left: -300vh !important;
}


:fullscreen ytd-live-chat-frame#chat #show-hide-button[class]{
    flex-grow: 0;
    flex-shrink:0;
    position:static;
    cursor: all-scroll;
}
:fullscreen ytd-live-chat-frame#chat #show-hide-button[class] *[class]{
    cursor: inherit;
}
:fullscreen ytd-live-chat-frame#chat iframe[class]{
    flex-grow: 100;
    flex-shrink:0;
    height: 0;
    position:static;
}


html{
    --f7-handle-color: #0cb8da;
}
html[dark]{
    --f7-handle-color: #0c74e4;
}
:fullscreen .resize-handle{

    position: absolute;
    top: 0;
    left: 0;
    bottom: 0;
    background: transparent;
    right: 0;
    border: 4px solid var(--f7-handle-color);
    z-index: 999;
    border-radius: inherit;
    box-sizing: border-box;
    pointer-events:none;
}

[moving] {
    cursor: all-scroll;
    --pointer-events:initial;
}

[moving] body {
    --pointer-events:none;
}

[moving] ytd-live-chat-frame#chat{

    --pointer-events:initial;
}


[moving] ytd-live-chat-frame#chat iframe {

    --pointer-events:none;
}


[moving="move"]  ytd-live-chat-frame#chat {
    background-color: var(--yt-spec-general-background-a);

}


[moving="move"] ytd-live-chat-frame#chat iframe {

    visibility: collapse;
}

[moving] * {
    pointer-events:var(--pointer-events) !important;

}

:fullscreen tyt-iframe-popup-btn{
    display: none !important;
}

[moving] tyt-iframe-popup-btn{
    display: none !important;
}



  `;

  const addCSS = () => {
    let text = createStyleText();
    let style = document.createElement('style');
    style.id = 'rvZ0t';
    style.textContent = text;
    document.head.appendChild(style);
  }

  const { Promise, requestAnimationFrame } = __CONTEXT__;

  let chatWindowWR = null;
  let showHideButtonWR = null;


  /* globals WeakRef:false */

  /** @type {(o: Object | null) => WeakRef | null} */
  const mWeakRef = typeof WeakRef === 'function' ? (o => o ? new WeakRef(o) : null) : (o => o || null); // typeof InvalidVar == 'undefined'

  /** @type {(wr: Object | null) => Object | null} */
  const kRef = (wr => (wr && wr.deref) ? wr.deref() : wr);



  let startX;
  let startY;
  let startWidth;
  let startHeight;


  let edge = 0;


  let initialLeft;
  let initialTop;

  let stopResize;
  let stopMove;


  const getXY = (e) => {
    let rect = e.target.getBoundingClientRect();
    let x = e.clientX - rect.left; //x position within the element.
    let y = e.clientY - rect.top;  //y position within the element.
    return { x, y }
  }

  let beforeEvent = null;

  function resizeWindow(e) {


    const chatWindow = kRef(chatWindowWR);
    if (chatWindow) {

      const mEdge = edge;

      if (mEdge == 4 || mEdge == 1) {

      } else if (mEdge == 8) {
      } else {
        return;
      }


      Promise.resolve(chatWindow).then(chatWindow => {
        let rect;

        if (mEdge == 4 || mEdge == 1) {

          let newWidth = startWidth + (startX - e.pageX);

          let newLeft = initialLeft + startWidth - newWidth;
          chatWindow.style.setProperty('--f3-w', newWidth + "px");
          chatWindow.style.setProperty('--f3-left', newLeft + "px");



          let newHeight = startHeight + (startY - e.pageY);

          let newTop = initialTop + startHeight - newHeight;
          chatWindow.style.setProperty('--f3-h', newHeight + "px");
          chatWindow.style.setProperty('--f3-top', newTop + "px");

          rect = {
            x: newLeft,
            y: newTop,
            w: newWidth,

            h: newHeight,


          };



        } else if (mEdge == 8) {

          let newWidth = startWidth + e.pageX - startX;
          let newHeight = startHeight + e.pageY - startY;

          chatWindow.style.setProperty('--f3-w', newWidth + "px");
          chatWindow.style.setProperty('--f3-h', newHeight + "px");


          rect = {
            x: initialLeft,
            y: initialTop,
            w: newWidth,

            h: newHeight,


          };

        }



        updateOpacity(chatWindow, rect, screen);

      })


      e.stopPropagation();
      e.preventDefault();


    }

  }

  function moveWindow(e) {


    const chatWindow = kRef(chatWindowWR);
    if (chatWindow) {

      Promise.resolve(chatWindow).then(chatWindow => {


        let newX = initialLeft + e.pageX - startX;
        let newY = initialTop + e.pageY - startY;

        chatWindow.style.setProperty('--f3-left', newX + "px");
        chatWindow.style.setProperty('--f3-top', newY + "px");



        updateOpacity(chatWindow, {
          x: newX,
          y: newY,
          w: startWidth,

          h: startHeight,


        }, screen);

      });



      e.stopPropagation();
      e.preventDefault();

    }
  }




  function initializeResize(e) {

    if (!document.fullscreenElement) return;

    if (e.target.id !== 'chat') return;


    const { x, y } = getXY(e);
    edge = 0;
    if (x < 16 && y < 16) { edge = -1 }
    else if (x < 16) edge = 4;
    else if (y < 16) edge = 1;
    else edge = 8;

    if (edge <= 0) return;

    startX = e.pageX;
    startY = e.pageY;

    const chatWindow = kRef(chatWindowWR);
    if (chatWindow) {

      Promise.resolve(chatWindow).then(chatWindow => {

        let rect = chatWindow.getBoundingClientRect();
        initialLeft = rect.x;
        initialTop = rect.y;



        startWidth = rect.width;
        startHeight = rect.height;


        chatWindow.style.setProperty('--f3-left', initialLeft + "px");
        chatWindow.style.setProperty('--f3-top', initialTop + "px");
        chatWindow.style.setProperty('--f3-w', startWidth + "px");
        chatWindow.style.setProperty('--f3-h', startHeight + "px");

      });

    }




    document.documentElement.setAttribute('moving', 'resize');

    document.documentElement.removeEventListener("mousemove", resizeWindow, false);
    document.documentElement.removeEventListener("mousemove", moveWindow, false);
    document.documentElement.removeEventListener("mouseup", stopResize, false);
    document.documentElement.removeEventListener("mouseup", stopMove, false);

    document.documentElement.addEventListener("mousemove", resizeWindow);
    document.documentElement.addEventListener("mouseup", stopResize);

  }


  let updateOpacityRid = 0;

  function updateOpacity(chatWindow, rect, screen) {

    let tid = ++updateOpacityRid;

    requestAnimationFrame(() => {


      if (tid !== updateOpacityRid) return;

      let { x, y, w, h } = rect;
      let [left, top, right, bottom] = [x, y, x + w, y + h];


      let opacityW = (Math.min(right, screen.width) - Math.max(0, left)) / w;
      let opacityH = (Math.min(bottom, screen.height) - Math.max(0, top)) / h;

      let opacity = Math.min(opacityW, opacityH);

      chatWindow.style.setProperty('--floating-window-opacity', Math.round(opacity * 100 * 5, 0) / 5 / 100);


    })





  }

  function initializeMove(e) {

    if (!document.fullscreenElement) return;



    const chatWindow = kRef(chatWindowWR);



    startX = e.pageX;
    startY = e.pageY;


    if (chatWindow) {

      Promise.resolve(chatWindow).then(chatWindow => {


        let rect = chatWindow.getBoundingClientRect();
        initialLeft = rect.x;
        initialTop = rect.y;



        startWidth = rect.width;
        startHeight = rect.height;


        chatWindow.style.setProperty('--f3-left', initialLeft + "px");
        chatWindow.style.setProperty('--f3-top', initialTop + "px");
        chatWindow.style.setProperty('--f3-w', startWidth + "px");
        chatWindow.style.setProperty('--f3-h', startHeight + "px");

      })


    }



    document.documentElement.setAttribute('moving', 'move');

    document.documentElement.removeEventListener("mousemove", resizeWindow, false);
    document.documentElement.removeEventListener("mousemove", moveWindow, false);
    document.documentElement.removeEventListener("mouseup", stopResize, false);
    document.documentElement.removeEventListener("mouseup", stopMove, false);

    document.documentElement.addEventListener("mousemove", moveWindow, false);
    document.documentElement.addEventListener("mouseup", stopMove, false);

    e.stopPropagation();
    e.preventDefault();

    beforeEvent = e;

  }


  function checkClick(beforeEvent, currentEvent) {

    if (currentEvent.timeStamp - beforeEvent.timeStamp < 300 && currentEvent.timeStamp - beforeEvent.timeStamp > 30) {

      document.documentElement.classList.add('no-floating');

    }

  }


  stopResize = (e) => {

    document.documentElement.removeAttribute('moving');
    document.documentElement.removeEventListener("mousemove", resizeWindow);
  }

  stopMove = (e) => {
    document.documentElement.removeAttribute('moving');
    document.documentElement.removeEventListener("mousemove", moveWindow);

    checkClick(beforeEvent, e)
    beforeEvent = null;
  }


  function reset() {

    document.documentElement.removeAttribute('moving');
    document.documentElement.removeEventListener("mousemove", resizeWindow, false);
    document.documentElement.removeEventListener("mousemove", moveWindow, false);
    document.documentElement.removeEventListener("mouseup", stopResize, false);
    document.documentElement.removeEventListener("mouseup", stopMove, false);


    startX = 0;
    startY = 0;
    startWidth = 0;
    startHeight = 0;


    edge = 0;


    initialLeft = 0;
    initialTop = 0;

    beforeEvent = null;


  }

  function setChat(chat) {

    let resizeHandle = HTMLElement.prototype.querySelector.call(chat, '.resize-handle')
    if (resizeHandle) return;


    let script = document.getElementById('rvZ0t') || (document.evaluate("//div[contains(text(), 'userscript-control[enable-customized-floating-window]')]", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null) || 0).singleNodeValue;
    if (!script) addCSS();


    resizeHandle = document.createElement("div");
    resizeHandle.className = "resize-handle";
    chat.appendChild(resizeHandle);
    resizeHandle = null;

    let chatWindow;
    let showHideButton;

    chatWindow = kRef(chatWindowWR);
    showHideButton = kRef(showHideButtonWR);



    if (chatWindow) chatWindow.removeEventListener("mousedown", initializeResize, false);
    if (showHideButton) showHideButton.removeEventListener("mousedown", initializeMove, false);


    chatWindow = chat;
    showHideButton = HTMLElement.prototype.querySelector.call(chat, '#show-hide-button');
    chatWindowWR = mWeakRef(chat)
    showHideButtonWR = mWeakRef(showHideButton);



    chatWindow.addEventListener("mousedown", initializeResize, false);
    showHideButton.addEventListener("mousedown", initializeMove, false);

    reset();

  }

  function noChat(chat) {

    let chatWindow;
    let showHideButton;

    chatWindow = kRef(chatWindowWR);
    showHideButton = kRef(showHideButtonWR);



    if (chatWindow) chatWindow.removeEventListener("mousedown", initializeResize, false);
    if (showHideButton) showHideButton.removeEventListener("mousedown", initializeMove, false);


    let resizeHandle = HTMLElement.prototype.querySelector.call(chat, '.resize-handle')
    if (resizeHandle) {
      resizeHandle.remove();
    }

    chat.removeEventListener("mousedown", initializeResize, false);


    showHideButton = HTMLElement.prototype.querySelector.call(chat, '#show-hide-button');

    if (showHideButton) showHideButton.removeEventListener("mousedown", initializeMove, false);


    reset();
  }

  document.addEventListener('fullscreenchange', () => {
    document.documentElement.classList.remove('no-floating')
  })

  customYtElements.whenRegistered('ytd-live-chat-frame', (proto) => {


    proto.attached = ((attached) => (function () { Promise.resolve(this).then(setChat); return attached.apply(this, arguments) }))(proto.attached);

    proto.detached = ((detached) => (function () { Promise.resolve(this).then(noChat); return detached.apply(this, arguments) }))(proto.detached);

    let chat = document.querySelector('ytd-live-chat-frame');
    if (chat) Promise.resolve(chat).then(setChat);

  })


})({ Promise, requestAnimationFrame });

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址