前言
随着互联网技术的不断更新,网页应用也越来越丰富。而视频是人们广泛使用的多媒体形式之一,自然引出了视频弹幕这一特殊效果。弹幕起源于日本,是指视频播放过程中由观众发送的滚动式文字评论。近年来,弹幕已经成为了许多娱乐视频和直播平台的标配。
为了方便我们在网页中使用视频弹幕,本文将介绍如何利用 Custom Elements (自定义元素)实现视频弹幕组件。
Custom Elements 简介
Custom Elements 是 Web Components 规范的一部分,其主要目的是帮助开发者创建可维护、可重用的自定义组件。通过使用 Custom Elements,我们可以封装网页中的某个元素,使其具备一定的特定功能和样式。
其中,Custom Element 主要由两个部分组成:定义(Definition)和实例化(Instantiation)。我们需要通过自定义元素定义去创建一个新的元素,然后在需要的地方实例化这个元素来达到预定的效果。
视频弹幕组件的功能设计
我们将视频弹幕组件设计成一个基于现代浏览器标签语义、无依赖库,通过简单的 HTML 结构实现对视频弹幕的展示和发送的组件。组件需具备以下功能:
- 显示一个占位符为视频区域的容器;
- 可以通过自有属性为弹幕容器和视频容器配置宽高;
- 可以在视频区域底部显示发送框,用户可以键入文字点击发送按钮发送信息;
- 在视频播放过程中,弹幕会从底部往上滚动,并在弹幕播放完毕之后自动归为。
相关知识点
在学习如何使用 Custom Elements 前,需要对以下知识点有一定的初步了解。
Shadow DOM
Shadow DOM 是 Web Components 规范中的一个重要概念。简单来说,Shadow DOM 允许我们在页面中创建独立的 DOM 树,它与页面原有的 DOM 树并不干扰。通过 Shadow DOM,我们可以达到对组件样式和结构的封装和隔离。
Custom Elements
Custom Elements 是 Web Components 规范的核心部分之一,它为我们提供了一种自定义元素的方式。可以定义一个自定义元素的构造函数和它的内部样式和逻辑。
实现方式
本文使用 HTML、CSS、JavaScript 技术栈,依赖原生浏览器 API 实现。实现主要是通过定义视频弹幕组件的构造函数,以及使用 Shadow DOM 和一些钩子函数处理组件的生命周期。
定义组件
定义视频弹幕组件需要通过定义一个新的类来完成,这个类需要继承 HTMLElement,以及实现构造函数和 connectedCallback 钩子函数(注意顺序)。
// javascriptcn.com 代码示例 class DanmakuVideo extends HTMLElement { constructor() { super() } connectedCallback() { const shadowRoot = this.attachShadow({ mode: 'open' }) shadowRoot.innerHTML = ` <!-- 弹幕和视频容器 --> <div class="danmaku-container"></div> <slot></slot> <!-- 发送框 --> <div class="send-container"> <input class="send-input" type="text" placeholder="键入消息..." /> <button class="send-btn">发送</button> </div> ` // 定义样式 shadowRoot.innerHTML += ` <style> /* ... */ </style> ` } } // 定义自定义元素 window.customElements.define('danmaku-video', DanmakuVideo)
视频和弹幕容器
我们使用 Shadow DOM 的 slot 元素来承载视频相关内容,使用 div 元素来承载弹幕相关内容。在构造函数中,我们通过 this.attachShadow({ mode: 'open' })
添加自定义元素的 Shadow DOM 子树,并将相关内容塞进去。
发送框
发送框的 HTML 结构在前面代码片段已经定义好了,我们将其通过位于组件自定义元素内的 js 呈现。代码如下:
// javascriptcn.com 代码示例 class DanmakuVideo extends HTMLElement { constructor() { super() } connectedCallback() { const shadowRoot = this.attachShadow({ mode: 'open' }) shadowRoot.innerHTML = ` <!-- 弹幕和视频容器 --> <div class="danmaku-container"></div> <slot></slot> <!-- 发送框 --> <div class="send-container"> <input class="send-input" type="text" placeholder="键入消息..." /> <button class="send-btn">发送</button> </div> ` // 获取相关元素 const danmakuContainer = shadowRoot.querySelector('.danmaku-container') const sendInput = shadowRoot.querySelector('.send-input') const sendBtn = shadowRoot.querySelector('.send-btn') // 发送按钮事件 sendBtn.addEventListener('click', () => { // 获取发送框输入内容 const message = sendInput.value if (!message) return // 发送弹幕... }) } }
这里我们通过 .querySelector 方法获取发送框相关元素,然后在发送按钮的单击事件中获取发送框输入的内容并进行处理。
弹幕滚动
弹幕的滚动需要利用 HTML5 video 元素及 Canvas API 实现。写法大致如下:
// javascriptcn.com 代码示例 class DanmakuVideo extends HTMLElement { constructor() { super() } connectedCallback() { const shadowRoot = this.attachShadow({ mode: 'open' }) shadowRoot.innerHTML = ` <!-- 弹幕和视频容器 --> <div class="danmaku-container"></div> <slot></slot> <!-- 发送框 --> <div class="send-container"> <input class="send-input" type="text" placeholder="键入消息..." /> <button class="send-btn">发送</button> </div> ` // 获取相关元素 const danmakuContainer = shadowRoot.querySelector('.danmaku-container') const sendInput = shadowRoot.querySelector('.send-input') const sendBtn = shadowRoot.querySelector('.send-btn') // 发送按钮事件 sendBtn.addEventListener('click', () => { // 获取发送框输入内容 const message = sendInput.value if (!message) return // 发送弹幕... }) const video = this.querySelector('video') video.addEventListener('play', () => { // 单位弹幕的高度 const danmakuH = 24 // 弹幕显示的最多行数 const danmakuRows = 8 // 弹幕速度 const speed = 120 // 弹幕的坐标 let danmakuPosY = 0 // 弹幕内容数组 let danmakuList = [] // 获取弹幕容器的高度 const dh = danmakuContainer.offsetHeight // 新增文字弹幕 addTextDanmaku(text: string) { const danmakuDiv = document.createElement('div') danmakuDiv.classList.add('danmaku-text') danmakuDiv.innerText = text danmakuContainer.appendChild(danmakuDiv) // 该弹幕相关的元素 const danmakuObj = { node: danmakuDiv, posX: danmakuDiv.offsetLeft, posY: danmakuPosY, timestamp: performance.now() } // 弹幕纵坐标更新 danmakuPosY += danmakuH if (danmakuPosY > dh) danmakuPosY = 0 // 加入弹幕列表 danmakuList.push(danmakuObj) } // 弹幕更新函数 const updateDanmaku = () => { // 计算时间差,单位是秒 const elapsed = (performance.now() - startTs) / 1000 // 清空 Canvas 画布 ctx.clearRect(0, 0, cw, ch) // 绘制弹幕 for (let i = 0; i < danmakuList.length; i++) { const danmakuObj = danmakuList[i] // 绘制弹幕 ctx.fillText(danmakuObj.text, danmakuObj.posX, danmakuObj.posY) // 计算移动后的弹幕位置 danmakuObj.posX -= speed * elapsed // 弹幕删除 if (danmakuObj.posX < -cw) { danmakuContainer.removeChild(danmakuObj.node) danmakuList.splice(i, 1) } } // 更新计时器 requestAnimationFrame(updateDanmaku) } // 获取 Canvas 画布 const canvas = document.createElement('canvas') danmakuContainer.appendChild(canvas) const ctx = canvas.getContext('2d') // 更新 Canvas 画布的宽高 const cw = danmakuContainer.offsetWidth const ch = danmakuContainer.offsetHeight canvas.width = cw canvas.height = ch // 开始计时器 const startTs = performance.now() updateDanmaku() }) } }
这个实现方式即实现了弹幕从底部往上滚动的效果。
完整代码展示
本文示例代码存放于 Github,可以查看 danmaku-video 项目进行查看、学习。
总结
总的来说,使用 Custom Elements 可以实现灵活的组件,这为我们提供了更多的灵活性和创造力。弹幕组件不仅是一个具有实用性又很有趣的东西,也为我们展示了如何使用 Custom Elements 构建优秀的无依赖库前端组件的方法。
来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/652ba8f97d4982a6ebd70b64