前端实现弹幕代码分享

   上次项目我们分的是图片网站,原本想的是加一个视频播放的功能嘞,但是奈何我们组就一个后端,三个前端,视频播放并不是我们网站的必要功能,所以就没去实现,但是项目结束了,闲着没事就看了一下弹幕的实现方式,在b站搜到了一个使用canvas实现弹幕的方法,看到人家的代码才知道自己写的代码有多low,人家写的代码是真的将功能分离,后期的可维护性和拓展性大大加强了。这里就来分享一下代码。
html并没有写过多的样式,大概是这样的,video用到的是原生的东西
在这里插入图片描述

html:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>canvas实现弹幕</title>
    <style>
        .content {
            width: 900px;
            margin: 60px auto;
            border: 1px solid red;
            position: relative;
        }

        #canvas {
            position: absolute;
            top: 0;
            right: 0;
            /* z-index: 10; */
        }

        .control {
            text-align: center;
        }

        input {
            vertical-align: middle;
        }
    </style>
</head>

<body>
    <div class="">
        <div class="content">
            <canvas id="canvas"></canvas>
            <video id="video" src="视频地址" height="auto" controls width="900"></video>
        </div>
        <div class="control">
        	//	弹幕内容
            <input id="ipt" placeholder="请输入弹幕" />
            //弹幕的颜色选择
            <input id="colorIpt" value="#cccccc" type="color" />
            //弹幕的字体大小选择
            <input id="fontIpt" min="20" max="60" value="26" type="range" />
            <button id="submit">点击发送</button>
        </div>
    </div>
    
</body>

</html>

js(这里我和那个视频上的一样,把实现弹幕的主要功能和调用以及生成假数据的函数分开)

let data = getDate(20)
// 获取数据的函数
function getDate(len) {
	let data = []
	/* 一个弹幕数据中需要有弹幕的具体值,弹幕的字体大小,弹幕的颜色,它出现在视频中的时间,以及他的速度
	*/
    for (let i = 0; i < len; i++) {
    	data.push({
    	value: `${i}条弹幕`,
        fontSize: 26,
        color: 'red',
        time: i * 2,
        speed: 1
    	})
	}
     return data
}
const canvas = document.getElementById('canvas')
const video = document.getElementById('video')
/* Barrages是实现的具体方法,需要传canvas(绘制弹幕的canvas),video(播放视频),数据
*/
barage = new Barrages({
	canvas,
	video,
    data
})
// 播放视频,弹幕播放
video.addEventListener('play', () => {
	barage.isPlay = true
    barage.render()
 })
// 视频暂停,弹幕暂停
video.addEventListener('pause', () => {
    barage.pause()
})
/* 声名一个对象,里边包含着弹幕的字体和颜色(这里给其一个默认值),之后修改颜色和字体大小的时候会改变这个对象的值
*/
let option = {
	color: '#cccccc',
    fontSize: 26
}
// 点击发送,插入数据
submit.addEventListener('click', () => {
	option.time = video.currentTime
    barage.setBarrage(option)
    ipt.value = ''
})
//弹幕内容
ipt.addEventListener('change', (event) => {
	option.value = event.target.value;
})
//弹幕颜色
colorIpt.addEventListener('change', (event) => {
	option.color = event.target.value;
})
//弹幕字体大小
fontIpt.addEventListener('change', (event) => {
	option.fontSize = +event.target.value;
})

具体实现函数

// 渲染弹幕数据的具体函数
class Barrage {
    constructor(options, ctx) {
        this.ctx = ctx
        // 设置是否初始化
        this.isInit = false
        // 判断是否还需要绘制
        this.flag = true
        // 归并到this上去
        Object.assign(this, options)
    }
    // 初始化
    init() {
        this.color = this.color || this.ctx.color
        this.fontSize = this.fontSize || this.ctx.fontSize
        this.speed = this.speed || this.ctx.speed
        // 获取宽度
        this.width = this.getWidth()
        // 获取宽度
        this.x = this.ctx.canvas.width
        // 获取高度
        this.y = Math.random() * this.ctx.canvas.height
        // 判断是否大于canvas的高度减去字体大小(如果大于就等于这个值)
        if (this.y > this.ctx.canvas.height - this.fontSize) {
            this.y = this.ctx.canvas.height - this.fontSize
        }
        if (this.y < this.fontSize) {
            this.y = this.fontSize
        }
        /* 上述的两个判断是为了避免弹幕出现在canvas外边(别忘了把字体大小算进去)*/
    }
    // 获取宽度(通过把弹幕内容放到html元素中来获弹幕的具体宽度)
    getWidth() {
        // 创建一个标签
        let span = document.createElement('span')
        span.innerText = this.value
        span.style.font = `${this.fontSize}px "Miscrosoft YaHei"`
        span.style.position = 'absolute'
        span.style.zIndex = -1
        //插入到body里边(方便获取宽度)
        document.body.appendChild(span)
        let width = span.clientWidth
        // 删除
        document.body.removeChild(span)
        return width
    }
    //将弹幕渲染到canvas上
    render() {
    	//指定字体大小
        this.ctx.context.font = `${this.fontSize}px "Miscrosoft YaHei"`
        // 指定字体颜色
        this.ctx.context.fillStyle = this.color
        // 指定绘制内容和绘制位置
        this.ctx.context.fillText(this.value, this.x, this.y)
    }
}
// 整体的流程控制
class Barrages {
    constructor(options) {
    	//判断是否有canvas和video
        if (!options.canvas || !options.video) {
            return
        }
        // 声明一个默认值(避免没有传值时的错误渲染)
        const defaultOptions = {
            fontSize: 26,
            color: 'green',
            data: [],
            speed: 1
        }
        // 把这些东西都归并到this上
        Object.assign(this, defaultOptions, options)
        // 初始化canvas
        this.canvas.width = this.video.clientWidth
        this.canvas.height = this.video.clientHeight
        // 取出canvas上下文(绘制canvas需要获取它的上下文)
        this.context = this.canvas.getContext('2d')
        // 将每个数据实例化(箭头函数的this)
        this.barrages = this.data.map((item) => new Barrage(item, this))
        // 是否需要播放弹幕
        this.isPlay = true
    }
    // 清除画布方法
    clear() {
        this.context.clearRect(0, 0, this.canvas.width, this.canvas.height)
    }
    //将新增的数据添加到数组中
    setBarrage(data) {
        this.data.push(data)
        this.barrages.push(new Barrage(data, this))
    }
    // 渲染方法
    render() {
        // 清空画布
        this.clear()
        // 渲染新的画布
        this.renderBarrages()
        //判断是否需要绘制弹幕
        if (this.isPlay) {
        	// 循环执行渲染函数(需要矫正一下this的值)
            requestAnimationFrame(this.render.bind(this))
        }
    }
    // 绘制弹幕方法
    renderBarrages() {
        // 获取当前视频的时间
        const time = this.video.currentTime
        // 遍历数据
        this.barrages.forEach(item => {
            // 遍历所有弹幕数据,通过判断时间来确定其是否应该显示
            if (item.time <= time && item.flag) {
                // 判断是否应该初始化
                if (!item.isInit) {
                    item.init()
                    item.isInit = true
                }
                /* 超出canvas不需要再绘制了原作者这里写的时this,是不对的,因为这里是箭头函数,this的指向的并不是每条弹幕数据的实例,如果是普通函数this的指向则是window,所以这里改成item(弹幕数据的实例对象)
                */
                if (item.x < -item.width) {
                    item.flag = false
                }
                //改变它的横轴位置
                item.x = item.x - item.speed
                // 调用其render方法
                item.render()
            }
        })
    }
    // 暂停方法
    pause() {
        this.isPlay = false
        console.log('pause')
    }
}

利用canvas实现的本质就是每次渲染的时候都去初始化canvas,然后根据数据实例化对象的x和y坐标,去绘制到canvas上,虽然我们看着像连续的,但是其背后是重新渲染的。

上边的便是我分享的代码,虽然有点代码搬运工的意思吧,但是看完人家写的代码,还是感觉自己写的太差了,之后要多培养这种将功能分离的思想,和将数据对象化的思想。下周继续努力吧!!!
这段代码的作者b站视频连接

  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
以下是一个简单的前端页面实现弹幕效果的示例: HTML代码: ```html <!DOCTYPE html> <html> <head> <title>弹幕效果</title> <meta charset="UTF-8"> <style> #danmu { position: relative; height: 300px; width: 500px; background-color: #EEE; overflow: hidden; } #danmu div { position: absolute; font-size: 14px; color: #FFF; white-space: nowrap; user-select: none; -webkit-user-select: none; transform: translateX(-100%); animation: danmu 5s linear; animation-fill-mode: forwards; } @keyframes danmu { to { transform: translateX(100%); } } </style> </head> <body> <div id="danmu"></div> <form onsubmit="return false;"> <input type="text" id="message"> <button onclick="send()">发送</button> </form> <script> var danmu = document.getElementById('danmu'); var message = document.getElementById('message'); function send() { var text = message.value; if (text.length > 0) { var div = document.createElement('div'); div.innerText = text; div.style.top = Math.floor(Math.random() * (danmu.offsetHeight - 20)) + 'px'; danmu.appendChild(div); message.value = ''; } } </script> </body> </html> ``` CSS代码: ```css #danmu { position: relative; height: 300px; width: 500px; background-color: #EEE; overflow: hidden; } #danmu div { position: absolute; font-size: 14px; color: #FFF; white-space: nowrap; user-select: none; -webkit-user-select: none; transform: translateX(-100%); animation: danmu 5s linear; animation-fill-mode: forwards; } @keyframes danmu { to { transform: translateX(100%); } } ``` JavaScript代码: ```javascript var danmu = document.getElementById('danmu'); var message = document.getElementById('message'); function send() { var text = message.value; if (text.length > 0) { var div = document.createElement('div'); div.innerText = text; div.style.top = Math.floor(Math.random() * (danmu.offsetHeight - 20)) + 'px'; danmu.appendChild(div); message.value = ''; } } ``` 解释: 1. HTML部分包含一个div元素和一个表单元素。div元素用于显示弹幕,表单元素用于输入弹幕内容。 2. CSS部分定义了弹幕容器的样式和弹幕元素的样式,其中弹幕元素使用了CSS动画来实现从左到右的滚动效果。 3. JavaScript部分定义了发送弹幕的函数,该函数会创建一个新的弹幕元素并添加到弹幕容器中。弹幕的位置使用了随机数来实现。 4. 在表单元素中使用onsubmit属性,并返回false,可以防止页面刷新。在发送按钮中调用send函数即可发送弹幕

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值