LoveCode 发表于 2024-11-14 14:31

B站网页版视频区域放大

本帖最后由 LoveCode 于 2024-11-14 14:43 编辑


在观看有关代码的视频时,如果字太小,使用该脚本可以临时放大特定区域便于查看。**它并没有调整清晰度**,只是放大区域 —— 类似放大镜的功能。

总结:只有在看代码的时候有用,比较鸡肋。



基本使用:

-   按住**鼠标中键**往**右下角**拖动,会**放大**所框选的区域,视频也会继续播放。
-   按住**鼠标中键**往**左上角**拖动、或者**鼠标中键双击**,会**还原**视频。

注意事项:

-   已经放大的区域无法继续放大。
-   鼠标按住之后,只能右下角、左上角两个移动方式。
-   **底部区域无法放大**,因为被进度条挡住了。
-   可能和一些鼠标手势的插件冲突。
-   因为框选区域太靠边,可能出现黑框,问题不大。



效果演示:






# 代码

```js
// ==UserScript==
// @name         B 站视频局部区域放大
// @namespace    http://tampermonkey.net/
// @version      0.0.1
// @description在 B 站观看视频时,可以局部放大视频区域,以便观看细节。
// @AuThor       You
// @license      MIT
// @match      https://www.bilibili.com/video/*
// @Icon         https://www.google.com/s2/favicons?sz=64&domain=bilibili.com
// @grant      none
// @run-at       document-end
// ==/UserScript==

(function () {
    'use strict';
    const video = document.querySelector('video');
    if (video) { zoomer(video); }

    /** 最大放大倍数 */
    const MaxScale = 10;

    /** 为 elem 元素实现鼠标绘制区域的放大功能 */
    function zoomer(elem) {
      /** 记录鼠标框选区域的元素 */
      let selectedRect = null;

      /** 初始化显示的矩形框 */
      function init_highlight_rect() {
            if (selectedRect) { return; }
            selectedRect = document.createElement('div');
            selectedRect.id = 'highlight-rect';
            selectedRect.style.padding = '0';
            selectedRect.style.margin = '0';
            selectedRect.style.position = 'absolute';
            selectedRect.style.border = '2px solid red';
            selectedRect.style.backgroundColor = 'transparent';
            selectedRect.style.pointerEvents = 'none';
            selectedRect.style.zIndex = 99999999;
            selectedRect.style.display = 'none';
            document.body.appendChild(selectedRect);
      }

      /** 重置显示的矩形框 */
      function reset_highlight_rect() {
            if (!selectedRect) { return; }
            selectedRect.style.display = 'none';
            selectedRect.style.width = '0';
            selectedRect.style.height = '0';
      }

      /** 获取元素 elem 的中心位置,基于 Page 页面坐标 */
      function get_elem_center(elem) {
            // 先获取元素基于视口的位置
            const rect = elem.getBoundingClientRect();
            // 然后计算元素的中心位置,基于视口的
            const centerX = rect.left + rect.width / 2;
            const centerY = rect.top + rect.height / 2;
            // 然后加上滚动条的偏移量,得到基于页面的坐标
            const pageX = centerX + document.documentElement.scrollLeft;
            const pageY = centerY + document.documentElement.scrollTop;
            return { x: pageX, y: pageY };
      }

      /** 放大元素的某个区域,这里的 startX、startY 是基于 Page 页面的 */
      function zoom_in(elem, startX, startY, width, height) {
            // 计算缩放比列
            const scaleX = elem.offsetWidth / width;
            const scaleY = elem.offsetHeight / height;
            const scale = Math.min(Math.min(scaleX, scaleY).toFixed(2), MaxScale);

            // 获取要放大区域的中心位置,基于 Page 页面的
            const left = startX + width / 2;
            const top = startY + height / 2;

            // 计算 elem 元素的中心,也是基于 Page 页面的
            const { x: elemLeft, y: elemTop } = get_elem_center(elem);

            // 计算把该区域中心平移到 elem 元素中心的距离
            const translateX = elemLeft - left;
            const translateY = elemTop - top;
            // 设置元素的 transform-origin 属性,使其缩放的中心点在矩形框的中心位置
            elem.style.transformOrigin = `${left - elem.offsetLeft}px, ${top - elem.offsetTop}px`;
            // 然后,设置元素的 transform 属性,先缩放,再平移
            elem.style.transform = `scale(${scale}) translate(${translateX}px, ${translateY}px)`;
      }

      /** 重置元素的缩放 */
      function zoom_reset(elem) {
            elem.style.transformOrigin = '';
            elem.style.transform = '';
      }

      /** 为元素 elem 绑定鼠标事。
         * - 当鼠标中键被按下、移动时可以显示矩形框
         * - 当鼠标中键松开时,可以放大或缩小元素
      */
      function bind_events(elem) {
            let isMouseDown = false;

            // 记录鼠标按下时的坐标,基于页面
            let startX = 0;
            let startY = 0;
            // 记录鼠标抬起时的坐标,基于页面
            let endX = 0;
            let endY = 0;
            /** 记录是否已经缩放过 */
            let isZoomed = false;

            /** 为 "" 表示还没有进行任何鼠标操作。
             *
             * 为 "zomin" 表示鼠标往右下方向移动,需要放大图片大小
             *
             * 为 "reset" 表示鼠标往左上方向移动,需要还原图片大小
             */
            let operation = "";

            init_highlight_rect();

            // 鼠标中键快速双击也能取消缩放
            let lastClickTime = Date.now();
            elem.addEventListener('mousedown', (e) => {
                // 只有鼠标中键点击才行
                if (e.button !== 1) { return; }
                // 防止默认行为 —— 出现向下的滚动
                e.preventDefault();

                // 判断是否为双击
                const now = Date.now();
                if (now - lastClickTime < 300) {
                  zoom_reset(elem);
                  isZoomed = false;
                  isMouseDown = false;
                  operation = "";
                  reset_highlight_rect();
                  return;
                }
                lastClickTime = now;

                isMouseDown = true;
                // 基于页面的坐标
                startX = e.pageX;
                startY = e.pageY;

                // 设置矩形框的起点,注意了,这个起点坐标是基于 Page 的
                selectedRect.style.left = `${startX}px`;
                selectedRect.style.top = `${startY}px`;
                selectedRect.style.display = 'block';
            });

            elem.addEventListener('mousemove', (e) => {
                if (!isMouseDown) { return; }
                // 计算鼠标移动的距离,基于 Page 坐标系
                endX = e.pageX;
                endY = e.pageY;
                const x = endX - startX;
                const y = endY - startY;

                // 鼠标是往右下方向移动,需要放大图片大小
                // 如果已经放大了,那就不能再放大了
                if (!isZoomed && x > 0 && y > 0) {
                  if (operation !== "zoomin") {
                        operation = "zoomin";
                        selectedRect.style.transformOrigin = '';
                        selectedRect.style.transform = '';
                  }
                }
                // 鼠标是往左上方向移动
                else if (x < 0 && y < 0) {
                  if (operation !== "reset") {
                        operation = "reset";
                        // rect 绕着起使位置旋转 180 度
                        selectedRect.style.transformOrigin = 'left top';
                        selectedRect.style.transform = 'rotate(180deg)';
                  }
                }
                // 其它情况不进行处理
                else { }

                // 然后绘制矩形框的宽高
                if (operation !== "") {
                  selectedRect.style.width = `${Math.abs(x)}px`;
                  selectedRect.style.height = `${Math.abs(y)}px`;
                }
            });

            // 鼠标松开时,隐藏矩形框,然后放大或缩小元素
            elem.addEventListener('mouseup', (e) => {
                if (!isMouseDown) { return; }
                isMouseDown = false;
                reset_highlight_rect();

                if (operation === "zoomin") {
                  zoom_in(elem, startX, startY,
                        Math.abs(endX - startX), Math.abs(endY - startY));
                  isZoomed = true;
                }
                else if (operation === "reset") {
                  zoom_reset(elem);
                  isZoomed = false;
                }

                operation = "";
            });

            // 鼠标移出元素时,隐藏矩形框,然后什么都不做
            elem.addEventListener('mouseout', () => {
                isMouseDown = false;
                operation = "";
                reset_highlight_rect();
            });
      }

      /** main code */
      bind_events(elem);
    }
})();
```



# 原理阐述

核心是利用 `transform` 属性进行多个变换:

1.   计算出放大的比例,利用 `scale` 进行缩放变换。
2.   利用 `translate` 平移变化,将所框选的区域移动到视频所在区域的中心位置。
3.   `<video>` 元素所在的父元素设置 `overflow: hidden` —— 至少 B 站视频不需要考虑这个。



图示说明:


冬天冷了多穿点 发表于 2024-11-14 14:49

感谢分享

Do_zh 发表于 2024-11-14 14:50

这个可以。值得学习。

nokia790 发表于 2024-11-14 14:57

感觉还不错。支持。

cliffsu 发表于 2024-11-14 14:58

厉害,很实用

kangta520 发表于 2024-11-14 15:09

执行不了?中键还是原有上下转功能

猫之茗 发表于 2024-11-14 15:11

感谢分享,晚上去试试。

natsuki777 发表于 2024-11-14 15:17

感谢分享

SN1t2lO 发表于 2024-11-14 15:37

系统自带的放大镜算是要下岗了

lerd 发表于 2024-11-14 15:38

感觉很实用啊,待会试一下
页: [1] 2 3 4
查看完整版本: B站网页版视频区域放大