html5使用canvas实现弹幕功能示例


Posted in HTML / CSS onSeptember 11, 2017

最近在着手开发弹幕视频网站,通过html5中的canvas实现了弹幕的功能。

那么闲言碎语不要讲,先说思路后上代码。

思路:从页面布局上来说就是将一块画布覆盖在了video标签产生的视频窗口之上,使用绝对定位就能实现了。最重要的就是js控制画布上弹幕的显示了,每一个弹幕都包装成一个对象,对象包含的属性有弹幕应该出现的时间,弹幕的颜色,弹幕是否是移动的以及弹幕的文本。弹幕对象拥有方法包含:设置弹幕的横纵坐标,弹幕的移动函数。实现的原理,在监听视频开始播放的事件,在视频开始播放时生成一个定时器,定时器每隔一个时间去遍历循环弹幕对象数组并根据对象的属性在画布的适当位置上绘制出弹幕,计时器中除了绘制弹幕的代码还有执行更新弹幕数组的代码。

下图是弹幕效果截屏

html5使用canvas实现弹幕功能示例

那么下面开始直接上代码:

(function () {
    window.onload=function () {
        var video = document.getElementsByTagName("video")[0]
        var cav = document.getElementsByTagName("canvas")[0]
        //设置常量canvas的高度以及宽度
        var cavWidth = 800 
        var cavHeight = 420
        cav.width=cavWidth
        cav.height=cavHeight 
        var ctx = cav.getContext("2d")
        //存储弹幕对象的数组
        var capObjs = []
        var lastItemTime
        var capHeight = 20 
        var inputEle = document.getElementsByClassName("caption-input-text")[0]
        var sendEle = document.getElementsByClassName("caption-sendButton")[0]
        var colorUl = document.getElementsByClassName("colorItems")[0]
        var ismoveInputEle = document.getElementsByClassName("caption-input-ismove")[0]
        //弹幕颜色
        var colors=["#fff","#FFCCCC","#CCFFCC","#CCCCFF","#FFFFCC","#CCFFFF"]
        var selectedColorIndex = 0
        var prevPlayTime = 0
        //测试数据的数组
        var testArrayCopy = []
        var capobjId = 0
        //弹幕在画布中高度可能值组成的数组
        var topObjs = [{blank:true , value : 20 ,index:0},
                        {blank:true , value : 50 ,index:1},
                        {blank:true , value : 80 ,index:2},
                        {blank:true , value : 110 ,index:3},
                        {blank:true , value : 140 ,index:4},
                        {blank:true , value : 170 ,index:5},
                        {blank:true , value : 200 ,index:6},
                        {blank:true , value : 230 ,index:7},
                        {blank:true , value : 260 ,index:8},
                        {blank:true , value : 290 ,index:9},
                        {blank:true , value : 320 ,index:10},
                        {blank:true , value : 350 ,index:11},
                        {blank:true , value : 380 ,index:12},
                        {blank:true , value : 410 ,index:13}]
//test data 测试数据
var testArray = [{content:"ABCDEFGHIJKLMNOPQRSTUVWXYZ",time:"1",ismove:false,colorIndex:0},
{content:"233333333333333",time:"2",ismove:true,colorIndex:0},
{content:"干杯,哈哈哈~~~~~~",time:"2",ismove:true,colorIndex:5},
{content:"干杯,哈哈哈~~~~~~",time:"2",ismove:true,colorIndex:4},
{content:"干杯,哈哈哈~~~~~~",time:"2",ismove:true,colorIndex:4},
{content:"干杯,哈哈哈~~~~~~",time:"2",ismove:true,colorIndex:0},
{content:"干杯,哈哈哈~~~~~~",time:"2",ismove:true,colorIndex:0},
{content:"233333333333333",time:"3",ismove:true,colorIndex:0},
{content:"233333333333333",time:"3",ismove:true,colorIndex:0},
{content:"233333333333333",time:"3",ismove:true,colorIndex:0},
{content:"233333333333333",time:"3",ismove:true,colorIndex:0},
{content:"233333333333333",time:"3",ismove:true,colorIndex:0},
{content:"233333333333333",time:"3",ismove:true,colorIndex:0},
{content:"233333333333333",time:"4",ismove:false,colorIndex:0},
{content:"233333333333333",time:"5",ismove:true,colorIndex:4},
{content:"233333333333333",time:"6",ismove:true,colorIndex:2},
{content:"233333333333333",time:"7",ismove:true,colorIndex:2},
{content:"233333333333333",time:"7",ismove:true,colorIndex:2},
{content:"233333333333333",time:"7",ismove:true,colorIndex:2},
{content:"233333333333333",time:"7",ismove:true,colorIndex:2},
{content:"233333333333333",time:"7",ismove:true,colorIndex:2},
{content:"233333333333333",time:"7",ismove:true,colorIndex:2},
{content:"233333333333333",time:"8",ismove:true,colorIndex:0},
{content:"233333333333333",time:"9",ismove:true,colorIndex:0},
{content:"233333333333333",time:"10",ismove:true,colorIndex:0},
{content:"老师说的非常好,我要好好学习了》》》》",time:"12",ismove:true,colorIndex:0},
{content:"老师说的非常好,我要好好学习了》》》》",time:"13",ismove:true,colorIndex:0},
{content:"老师说的非常好,我要好好学习了》》》》",time:"14",ismove:true,colorIndex:2},
{content:"老师说的非常好,我要好好学习了》》》》",time:"15",ismove:false,colorIndex:0},
{content:"老师说的非常好,我要好好学习了》》》》",time:"16",ismove:true,colorIndex:2},
{content:"老师说的非常好,我要好好学习了》》》》",time:"17",ismove:true,colorIndex:3},
{content:"老师说的非常好,我要好好学习了》》》》",time:"18",ismove:true,colorIndex:2},
{content:"老师说的非常好,我要好好学习了》》》》",time:"19",ismove:true,colorIndex:0},
{content:"老师说的非常好,我要好好学习了》》》》",time:"20",ismove:true,colorIndex:3},
{content:"老师说的非常好,我要好好学习了》》》》",time:"21",ismove:true,colorIndex:0},
{content:"老师说的非常好,我要好好学习了》》》》",time:"22",ismove:true,colorIndex:0},
{content:"老铁们,小礼物走一波了,小汽车小火箭刷起来吧=========",time:"23",ismove:true,colorIndex:0},
{content:"老铁们,小礼物走一波了,小汽车小火箭刷起来吧=========",time:"24",ismove:true,colorIndex:0},
{content:"老铁们,小礼物走一波了,小汽车小火箭刷起来吧=========",time:"25",ismove:true,colorIndex:3},
{content:"老铁们,小礼物走一波了,小汽车小火箭刷起来吧=========",time:"26",ismove:true,colorIndex:0},
{content:"老铁们,小礼物走一波了,小汽车小火箭刷起来吧=========",time:"27",ismove:true,colorIndex:5},
{content:"老铁们,小礼物走一波了,小汽车小火箭刷起来吧=========",time:"28",ismove:false,colorIndex:5},
{content:"老铁们,小礼物走一波了,小汽车小火箭刷起来吧=========",time:"29",ismove:true,colorIndex:5},
{content:"老铁们,小礼物走一波了,小汽车小火箭刷起来吧=========",time:"30",ismove:true,colorIndex:5},
{content:"马上就下课了,瓦罗蓝大陆走起了~~~",time:"31",ismove:true,colorIndex:5},
{content:"马上就下课了,瓦罗蓝大陆走起了~~~",time:"32",ismove:true,colorIndex:2},
{content:"马上就下课了,瓦罗蓝大陆走起了~~~",time:"33",ismove:true,colorIndex:2},
{content:"马上就下课了,瓦罗蓝大陆走起了~~~",time:"33",ismove:true,colorIndex:5},
{content:"马上就下课了,瓦罗蓝大陆走起了~~~",time:"34",ismove:true,colorIndex:5},
{content:"马上就下课了,瓦罗蓝大陆走起了~~~",time:"35",ismove:true,colorIndex:5},
{content:"马上就下课了,瓦罗蓝大陆走起了~~~",time:"36",ismove:true,colorIndex:2},
{content:"马上就下课了,瓦罗蓝大陆走起了~~~",time:"37",ismove:true,colorIndex:2}]
        //将测试数据备份
        copyArray(testArray , testArrayCopy)
        /*弹幕对象的构造函数,参数分别是:1.ismove:弹幕是否是移动的弹幕,2.spe:弹幕的移动速度,3.col:弹幕的颜色,4.text:弹幕的文本*/
        /*原型链方法 setTopValue设置纵坐标,setLeftValue设置横坐标,moving完成坐标的改变,setId完成id值的设置*/
        function Caption( ismove , spe , col , text ) {
            this.isMove = ismove
            this.speed = spe
            this.color = col || "#ff0"
            this.content = text
            this.latestTime = 0 
            this.width = text.length * 20 
            this.id = 0
            this.topIndex = 0
            this.occupyPos = true 
            this.top = 300
            this.left = 0
            this.setLeftValue()
            this.setTopValue()
        }
        Caption.prototype.setTopValue = function  () {
            for(var i = 0 ,len = topObjs.length ; i < len ; i++){
                if (topObjs[i].blank) {
                    this.top = topObjs[i].value
                    this.topIndex = i
                    topObjs[i].blank = false 
                    break
                }
            }
        }
        Caption.prototype.setLeftValue = function  () {
            if (this.isMove) {
                this.left = cavWidth
            }
            else {
                var contentLength = this.content.length
                var nowItemLeft = 420 - contentLength * 9
                this.left = nowItemLeft
            }
        }
        Caption.prototype.moving = function () {
            if (this.isMove) {
                this.left-=this.speed
                if ( this.left + this.width < cavWidth && this.occupyPos) {
                    this.occupyPos = false 
                    topObjs[this.topIndex].blank = true 
                }
            } 
            else{
                this.latestTime += 1
                if (this.latestTime > 450) {
                    topObjs[this.topIndex].blank = true 
                }
            }
        }
        Caption.prototype.setId = function  () {
            this.id = capobjId
            capobjId++
        }

        var cap1 = new Caption(  false , 1 , 0 , "小礼物走一波,双击6666。。。。")
        capObjs.push(cap1)
        cap1.setId()
        
        //循环遍历数组,根据对象的属性绘制在画布上
        function drawAllText () {
            ctx.clearRect( 0 , 0 , cavWidth , cavHeight)
            ctx.beginPath()
            for(var i=0 , len = capObjs . length ; i < len ; i++ ){
                ctx.fillStyle = capObjs[i].color
                ctx.font = "bold 20px Courier New"
                ctx.fillText( capObjs[i].content , capObjs[i].left , capObjs[i].top )
                ctx.closePath()
                capObjs[i].moving()
                // if (capObjs[i].left < - cavWidth ) {
                    // capObjs.splice (i ,1)
                    // if excute this statement , will has fault because some item in array is null
                    // solution is : write a new function to refresh the array   
                // }
            }
        }
        
        //更新数组,当对象已经超出范围的时候从数组删除这个对象
        function refreshObjs(objs) {
            for (var i = objs.length - 1; i >= 0; i--) {
                if (objs[i].left < - cavWidth || objs[i].latestTime > 450 ) {
                    objs.splice(i , 1)
                }

            }
        }
        
        //更新保存弹幕对象的数组
        function updateArray () {
            var now = parseInt( video.currentTime )
            for (var i = testArray.length - 1; i >= 0; i--) {
                var nowItemTime = parseInt(testArray[i].time) 
                if ( nowItemTime == now ) {
                    //首次写的控制高度的方式,空间利用不充分,后来改为setTopValue中的方式
                    // var nowItemLeft = getLeftValue(testArray[i])
                    // var diffTime = Math.abs(nowItemTime - lastItemTime)
                    // if (diffTime < 6) { 
                    //     capHeight += 30
                    //     capHeight = capHeight > 400 ? 20 : capHeight
                    // }    
                    var temcolor = colors[testArray[i].colorIndex]
                    var temcap = new Caption (  testArray[i].ismove , 1 , temcolor , testArray[i].content  )
                    capObjs.push(temcap)
                    capObjs[capObjs.length - 1].setId()
                    temcap = null
                    testArray.splice(i,1)
                }
            }
        }
        
        //当用户点击send发送弹幕的回调函数
        function sendCaption (argument) {
            var inputEleTxt = inputEle.value
            var now = parseInt( video.currentTime )
            var inputIsmoveValue = ismoveInputEle.checked
            var temObj = {content:inputEleTxt,time:now,ismove:inputIsmoveValue,colorIndex:selectedColorIndex}
            testArray.push(temObj)
            inputEle.value = ""
        }

        // function getLeftValue (obj) {
        //     if (obj.ismove) {
        //         return 0
        //     }
        //     else {
        //         var contentLength = obj.content.length
        //         var nowItemLeft = 420 - contentLength * 9
        //         return nowItemLeft
        //     }
        // }
        
        //重新启动canvas,用在人为导致进度条时间的改变
        function reinitCav (argument) {
            // testArray = testArrayCopy
            copyArray(testArrayCopy , testArray)
            capObjs = []
            capHeight = 0
            clearInterval(canvasTimer)
            canvasTimer = null
            initCanvas()
        }

        var canvasTimer = null 
        
        //初始化canvas,用在开始播放时
         function initCanvas () {
             if (canvasTimer == null ) {
                canvasTimer = setInterval(function (argument) {
                    drawAllText()
                    updateArray()
                    refreshObjs(capObjs)
                },10)
             }
            
        }//end function initCanvas
        
        //复制数组
        function copyArray (arr1 , arr2) {
            for (var i =0 , len=arr1.length ; i < len ; i++) {
                    arr2[i] = arr1[i]
                }    
        }

        //color select event 用户发送弹幕的颜色控制代码
        colorUl.addEventListener("click", function( e ){
            var prevSelectItemId = ""
            switch (selectedColorIndex) {
                case 0:
                    prevSelectItemId = "colorItemFrist"
                    break;
                case 1:
                    prevSelectItemId = "colorItemSecond"
                    break;
                case 2:
                    prevSelectItemId = "colorItemThrid"
                    break;
                case 3:
                    prevSelectItemId = "colorItemFourth"
                    break;
                case 4:
                    prevSelectItemId = "colorItemFifth" 
                    break;
                case 5:
                    prevSelectItemId = "colorItemSixth"
                    break;
                default:
                    // statements_def
                    break;
            }
            var prevSelectItem = document.getElementById(prevSelectItemId)
            prevSelectItem.className = ""
            var eventTarget = e.target
            eventTarget.className = "selectedColor"
            var eveTarId = eventTarget.id.substring(9)
            switch (eveTarId) {
                case "Frist":
                    selectedColorIndex = 0
                    break;
                case "Second":
                    selectedColorIndex = 1
                    break;
                case "Thrid":
                    selectedColorIndex = 2
                    break;
                case "Fourth":
                    selectedColorIndex = 3
                    break;
                case "Fifth":
                    selectedColorIndex = 4
                    break;
                case "Sixth":
                    selectedColorIndex = 5
                    break;
                default:
                    // statements_def
                    break;
            }
        }, false)

        video.addEventListener("playing" , function () {
            initCanvas()
        })
        
        //进度条改变执行代码
        video.addEventListener("timeupdate", function  () {
            var nowPlayTime = video.currentTime
            var diffTime = Math.abs(nowPlayTime - prevPlayTime)
            prevPlayTime = nowPlayTime
            if (diffTime > 1) {
                reinitCav()
            }
        }, false)
        
        //视频暂停执行代码
        video.addEventListener("pause" , function () {
            clearInterval(canvasTimer)
            canvasTimer = null 
        })
        
        //点击send的监听事件
        sendEle.addEventListener("click" , sendCaption)
        
        //input的回车监听事件
        inputEle.addEventListener("keydown", function(e) {
            var keynum = 0
            keynum = window.event ? e.keyCode : e.which
            if (keynum == 13) {
                sendCaption()
            }
        })


        var aaaa = function() {
            alert(1)
        }
        aaaa()
        // function b(aaaa){
        //     return aaaa()
        // }
        // b()    
    }//end
})()

希望能够对想要制作弹幕的同学有所帮助,还可以去github下载完整的项目代码:gitbub项目地址

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

HTML / CSS 相关文章推荐
css3动画过渡实现鼠标跟随导航效果
Feb 08 HTML / CSS
基于CSS3实现的黑色个性导航菜单效果
Sep 14 HTML / CSS
利用CSS3实现折角效果实例源码
Sep 28 HTML / CSS
CSS3 Notes: -webkit-box-reflect实现倒影的实例
Dec 08 HTML / CSS
Css3新特性应用之视觉效果实例
Dec 12 HTML / CSS
使用HTML5的链接预取功能(link prefetching)给网站提速
Dec 13 HTML / CSS
Data URI scheme详解和使用实例及图片base64编码实现方法
May 08 HTML / CSS
HTML5实现预览本地图片
Feb 17 HTML / CSS
浅谈Html5中视频 音频标签 进度条的问题
Jul 26 HTML / CSS
做一个能自适应高度的textarea的示例代码
Sep 06 HTML / CSS
HTML5实现应用程序缓存(Application Cache)
Jun 16 HTML / CSS
HTML静态页面获取url参数和UserAgent的实现
Aug 05 HTML / CSS
基于HTML5+CSS3实现简单的时钟效果
Sep 11 #HTML / CSS
html5超简单的localStorage实现记住密码的功能实现
Sep 07 #HTML / CSS
HTML5本地存储和本地数据库实例详解
Sep 05 #HTML / CSS
浅谈html5之sse服务器发送事件EventSource介绍
Aug 28 #HTML / CSS
HTML5实现可缩放时钟代码
Aug 28 #HTML / CSS
HTML5操作WebSQL数据库的实例代码
Aug 26 #HTML / CSS
HTML5自定义属性前缀data-及dataset的使用方法(html5 新特性)
Aug 24 #HTML / CSS
You might like
PHP程序员编程注意事项
2008/04/10 PHP
延长phpmyadmin登录时间的方法
2011/02/06 PHP
通过5个php实例细致说明传值与传引用的区别
2012/08/08 PHP
微信自定义菜单的创建/查询/取消php示例代码
2016/08/05 PHP
php的laravel框架快速集成微信登录的方法
2016/12/12 PHP
tp5(thinkPHP5框架)时间查询操作实例分析
2019/05/29 PHP
javascript读取xml
2006/11/04 Javascript
10款新鲜出炉的 jQuery 插件(Ajax 插件,有幻灯片、图片画廊、菜单等)
2011/06/08 Javascript
Javascript表格翻页效果的具体实现
2013/10/05 Javascript
JavaScript事件学习小结(一)事件流
2016/06/09 Javascript
浅谈JS函数定义方式的区别
2016/10/30 Javascript
JS中用三种方式实现导航菜单中的二级下拉菜单
2016/10/31 Javascript
微信小程序 wxapp地图 map详解
2016/10/31 Javascript
AngularJS验证信息框架的封装插件用法【w5cValidator扩展插件】
2016/11/03 Javascript
详解Node.js:events事件模块
2016/11/24 Javascript
详解angularJS自定义指令间的相互交互
2017/07/05 Javascript
nodejs中解决异步嵌套循环和循环嵌套异步的问题
2017/07/12 NodeJs
详解使用nvm管理多版本node的方法
2017/08/30 Javascript
微信小程序分享功能之按钮button 边框隐藏和点击隐藏
2018/06/14 Javascript
JS构造一个html文本内容成文件流形式发送到后台
2018/07/31 Javascript
详解React项目如何修改打包地址(编译输出文件地址)
2019/03/21 Javascript
原生JS 实现的input输入时表格过滤操作示例
2019/08/03 Javascript
微信小程序webview组件交互,内联h5页面并网页实现微信支付实现解析
2019/08/16 Javascript
vue-cli设置css不生效的解决方法
2020/02/07 Javascript
jquery实现异步文件上传ajaxfileupload.js
2020/10/23 jQuery
Vue常用API、高级API的相关总结
2021/02/02 Vue.js
[59:44]2018DOTA2亚洲邀请赛 3.31 小组赛 B组 paiN vs iG
2018/03/31 DOTA
解决pycharm的Python console不能调试当前程序的问题
2019/01/20 Python
Python socket模块方法实现详解
2019/11/05 Python
45个非常奇妙的CSS3 特性应用示例
2012/01/01 HTML / CSS
现金出纳岗位职责
2014/03/15 职场文书
银行进社区活动总结
2014/07/07 职场文书
2014年中班下学期工作总结
2014/12/11 职场文书
2014年骨干教师工作总结
2014/12/19 职场文书
小学班主任评语
2014/12/29 职场文书
python区块链持久化和命令行接口实现简版
2022/05/25 Python