GPT解释:

这个代码定义了一个名为 Pt 的 JavaScript 类,用于实现滚动视频播放效果。以下是该类的主要功能和工作原理的简要说明:

构造函数

constructor({
    src: d, 
    scrollyVideoContainer: c, 
    cover: h = !0, 
    sticky: T = !0, 
    full: v = !0, 
    trackScroll: P = !0, 
    transitionSpeed: r = 8, 
    frameThreshold: I = .1, 
    useWebCodecs: C = !0, 
    debug: p = !1
})
  • 参数

    • src: 视频源 URL。
    • scrollyVideoContainer: 包含视频的 DOM 容器。
    • cover: 是否覆盖容器。
    • sticky: 是否使用粘性定位。
    • full: 是否全屏显示。
    • trackScroll: 是否跟踪滚动。
    • transitionSpeed: 过渡速度。
    • frameThreshold: 帧阈值。
    • useWebCodecs: 是否使用 WebCodecs API。
    • debug: 是否启用调试模式。
  • 初始化过程

    • 检查和设置参数。
    • 创建和配置视频元素。
    • 设置容器样式。
    • 根据浏览器类型进行特定处理。
    • 设置滚动监听和视频元数据加载处理。
    • 绑定窗口大小调整事件。

方法

setCoverStyle(d)

设置视频或画布元素的覆盖样式,以确保视频或画布适应容器。

decodeVideo()

使用 WebCodecs API 解码视频帧,并将帧存储在 frames 数组中。如果解码失败,则回退到视频元素加载。

paintCanvasFrame(d)

在画布上绘制指定帧。

transitionToTargetTime(d)

根据滚动位置平滑过渡视频播放时间。

setTargetTimePercent(d, c)

根据滚动百分比设置目标播放时间,并触发过渡。

destroy()

销毁实例,移除所有事件监听器并清空容器内容。

事件处理

  • 滚动事件:根据滚动位置更新视频播放时间。
  • 窗口调整大小事件:调整视频或画布的样式以适应新的窗口大小。
  • 视频元数据加载事件:初始化滚动位置或设置初始播放时间。

主要功能

  • 滚动驱动的视频播放:根据页面滚动位置动态调整视频播放时间。
  • 视频解码与渲染:使用 WebCodecs API 解码视频帧,并在画布上绘制帧以实现更平滑的播放效果。
  • 响应式设计:根据窗口大小调整视频或画布样式,以确保良好的用户体验。
  • 调试模式:提供详细的调试信息,便于开发和问题排查。

这个类主要用于创建一个滚动驱动的视频播放效果,适用于需要在页面滚动时动态调整视频播放的场景,如互动叙事、广告展示等。

实现代码

    class Pt {
        constructor({src: d, scrollyVideoContainer: c, cover: h=!0, sticky: T=!0, full: v=!0, trackScroll: P=!0, transitionSpeed: r=8, frameThreshold: I=.1, useWebCodecs: C=!0, debug: p=!1}) {
            if (typeof document != "object") {
                console.error("ScrollyVideo must be initiated in a DOM context");
                return
            }
            if (!c) {
                console.error("scrollyVideoContainer must be a valid DOM object");
                return
            }
            if (!d) {
                console.error("Must provide valid video src to ScrollyVideo");
                return
            }
            if (c instanceof Element)
                this.container = c;
            else if (typeof c == "string") {
                if (this.container = document.getElementById(c),
                !this.container)
                    throw new Error("scrollyVideoContainer must be a valid DOM object")
            } else
                throw new Error("scrollyVideoContainer must be a valid DOM object");
            this.src = d,
            this.transitionSpeed = r,
            this.frameThreshold = I,
            this.useWebCodecs = C,
            this.cover = h,
            this.sticky = T,
            this.full = v,
            this.trackScroll = P,
            this.debug = p,
            this.video = document.createElement("video"),
            this.video.src = d,
            this.video.preload = "auto",
            this.video.tabIndex = 0,
            this.video.autobuffer = !0,
            this.video.playsInline = !0,
            this.video.muted = !0,
            this.video.pause(),
            this.video.load(),
            this.container.appendChild(this.video),
            T && (this.container.style.display = "block",
            this.container.style.position = "sticky",
            this.container.style.top = "0"),
            v && (this.container.style.width = "100%",
            this.container.style.height = "100vh",
            this.container.style.overflow = "hidden"),
            h && this.setCoverStyle(this.video);
            const k = new zt().getEngine();
            this.isSafari = k.name === "WebKit",
            p && this.isSafari && console.info("Safari browser detected"),
            this.currentTime = 0,
            this.targetTime = 0,
            this.canvas = null,
            this.context = null,
            this.frames = [],
            this.frameRate = 0,
            this.updateScrollPercentage = t=>{
                const e = this.container.parentNode.getBoundingClientRect()
                , i = -e.top / (e.height - window.innerHeight);
                this.debug && console.info("ScrollyVideo scrolled to", i),
                this.setTargetTimePercent(i, t)
            }
            ,
            this.trackScroll ? (window.addEventListener("scroll", this.updateScrollPercentage),
            this.video.addEventListener("loadedmetadata", ()=>this.updateScrollPercentage(!0), {
                once: !0
            })) : this.video.addEventListener("loadedmetadata", ()=>this.setTargetTimePercent(0, !0), {
                once: !0
            }),
            this.resize = ()=>{
                this.debug && console.info("ScrollyVideo resizing..."),
                this.cover && this.setCoverStyle(this.canvas || this.video),
                this.paintCanvasFrame(Math.floor(this.currentTime * this.frameRate))
            }
            ,
            window.addEventListener("resize", this.resize),
            this.video.addEventListener("progress", this.resize),
            this.decodeVideo()
        }
        setCoverStyle(d) {
            if (this.cover) {
                d.style.position = "absolute",
                d.style.top = "50%",
                d.style.left = "50%",
                d.style.transform = "translate(-50%, -50%)",
                d.style.minWidth = "101%",
                d.style.minHeight = "101%";
                const {width: c, height: h} = this.container.getBoundingClientRect()
                , T = d.videoWidth || d.width
                , v = d.videoHeight || d.height;
                this.debug && console.info("Container dimensions:", [c, h]),
                this.debug && console.info("Element dimensions:", [T, v]),
                c / h > T / v ? (d.style.width = "100%",
                d.style.height = "auto") : (d.style.height = "100%",
                d.style.width = "auto")
            }
        }
        decodeVideo() {
            this.useWebCodecs && this.src && kt(this.src, d=>{
                this.frames.push(d)
            }
            , this.debug).catch(()=>{
                this.debug && console.error("Error encountered while decoding video"),
                this.frames = [],
                this.video.load()
            }
            ).then(()=>{
                if (this.frames.length === 0) {
                    this.debug && console.error("No frames were received from webCodecs");
                    return
                }
                this.frameRate = this.frames.length / this.video.duration,
                this.debug && console.info("Received", this.frames.length, "frames"),
                this.canvas = document.createElement("canvas"),
                this.context = this.canvas.getContext("2d"),
                this.video.style.display = "none",
                this.container.appendChild(this.canvas),
                this.cover && this.setCoverStyle(this.canvas),
                this.paintCanvasFrame(Math.floor(this.currentTime * this.frameRate))
            }
            )
        }
        paintCanvasFrame(d) {
            if (this.canvas) {
                const c = this.frames[d];
                if (c) {
                    this.debug && console.info("Painting frame", d),
                    this.canvas.width = c.width,
                    this.canvas.height = c.height;
                    const {width: h, height: T} = this.container.getBoundingClientRect();
                    h / T > c.width / c.height ? (this.canvas.style.width = "100%",
                    this.canvas.style.height = "auto") : (this.canvas.style.height = "100%",
                    this.canvas.style.width = "auto"),
                    this.context.drawImage(c, 0, 0, c.width, c.height)
                }
            }
        }
        transitionToTargetTime(d) {
            if (this.debug && console.info("Transitioning targetTime:", this.targetTime, "currentTime:", this.currentTime),
            isNaN(this.targetTime) || Math.abs(this.currentTime - this.targetTime) < this.frameThreshold) {
                this.video.pause(),
                this.transitioning = !1;
                return
            }
            this.targetTime > this.video.duration && (this.targetTime = this.video.duration),
            this.targetTime < 0 && (this.targetTime = 0);
            const c = this.targetTime - this.currentTime;
            if (this.canvas)
                this.currentTime += c / (256 / this.transitionSpeed),
                d && (this.currentTime = this.targetTime),
                this.paintCanvasFrame(Math.floor(this.currentTime * this.frameRate));
            else if (d || this.isSafari || this.targetTime - this.currentTime < 0)
                this.video.pause(),
                this.currentTime += c / (64 / this.transitionSpeed),
                d && (this.currentTime = this.targetTime),
                this.video.currentTime = this.currentTime;
            else {
                const h = Math.max(Math.min(c * 4, this.transitionSpeed, 16), 1);
                this.debug && console.info("ScrollyVideo playbackRate:", h),
                isNaN(h) || (this.video.playbackRate = h,
                this.video.play()),
                this.currentTime = this.video.currentTime
            }
            typeof requestAnimationFrame == "function" && requestAnimationFrame(()=>this.transitionToTargetTime())
        }
        setTargetTimePercent(d, c) {
            this.targetTime = Math.max(Math.min(d, 1), 0) * (this.frames.length && this.frameRate ? this.frames.length / this.frameRate : this.video.duration),
            !(!c && Math.abs(this.currentTime - this.targetTime) < this.frameThreshold) && (!c && this.transitioning || (!this.canvas && !this.video.paused && this.video.play(),
            this.transitioning = !0,
            this.transitionToTargetTime(c)))
        }
        destroy() {
            this.debug && console.info("Destroying ScrollyVideo"),
            this.trackScroll && window.removeEventListener("scroll", this.updateScrollPercentage),
            window.removeEventListener("resize", this.resize),
            this.container && (this.container.innerHTML = "")
        }
    }