弹幕的实现(纯前端)

一、前言

  今天浏览某网站看到一个活动页有内嵌的弹幕模块(图一),但是看到移动的弹幕重叠很多,不忍直视啊。突然想起很久之前自己写写过类似的弹幕,就翻出来看了一下,呵,也是不忍直视的,最后再附上当年的效果以及代码;

 

二、大话几点

1、弹幕应用场景,视频中,直播中,微信墙等;

2、弹幕增加了大家的互动性,不再是单纯的傻白甜的看着视频,还可以吐吐槽,增加趣味性,但对于密集恐惧症的我,每当一大波弹幕来袭,我习惯性的cut off;

3、现在视频类的弹幕页面一般内嵌到视频中,微信墙等互动弹幕页面一般用h5实现,呈现的方式都差不多,有的弹幕可以每条进行操作,有的只能看。有的弹幕每条弹幕还要根据用户的相关等级,然后展示不同的效果,满足不同玩家的诉求,各种玩;

4、好的弹幕应该至少满足:弹幕分布密度,出现时机,速度,颜色,字体大小等都要做的相得益彰,至少能吸引大家进行互动。这就要考虑要一定的算法了,后面说。比如b站的弹幕就做的很好;

5、弹幕的后台实现可以通过websocket实现,当然也可以借助node实现。当用户输入弹幕,弹幕需经过特殊处理,比如经过第三方(数美等)过滤敏感关键字等,最后再把内容下发,觜最后展示;

三、思考:怎么做到弹幕均匀排布,不会重叠呢?还有速度控制问题。

1、可以想象把弹幕面板分成几个管道,每条弹幕随机分配(当然不是简单的随机,应该加上点概率论的知识)到每条管道中,当前方有弹幕时候,该弹幕的位置就要与前方弹幕保持一定计算的距离;

2、每条弹幕的速速要结合当前弹幕的长度和移动的位置去动态的添加初始速度,当然运行速度可以可以有linear ease-in ease-out ...等把控;

四、最后

很久前写的弹幕,没有对弹幕分布和速度进行把控,当然也没有后台,纯粹前端展示了,我就不折腾了。

 

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>barrage</title>
  <style>
    .box{
      width: 800px;
      height: 500px;
      margin:0 auto;
    }
    .barrage-container-wrap{
        width: 100%;
        height: 500px;
        position: relative;
        overflow: hidden;
        background: url('./img/bg.jpg') no-repeat;
        background-size: 100% 100%;
    }
    .barrage-container{
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 30px;
        cursor: default;
        -webkit-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        user-select: none;
    }
    .barrage-item{
        position:absolute;
        top:0;
        left: 100%;
        white-space: nowrap;
        cursor: pointer;
        color:#fff;
    }
    .barrage-item .barrage-tip{
        display: none;
        position: absolute;
        top:-26px;
        padding: 7px 15px;
        line-height: 12px;
        font-size: 12px;
        color: #f20606;
        background-color: #fff;
        white-space: nowrap;
        border: 1px solid #ddd;
        border-radius: 8px;
        -webkit-box-shadow: 0 0 10px 1px rgba(0,0,0,.1);
        box-shadow: 0 0 10px 1px rgba(0,0,0,.1);
        -webkit-transform-origin: 15px 100%;
        -ms-transform-origin: 15px 100%;
        transform-origin: 15px 100%;
        webkit-animation: tipScale cubic-bezier(.22,.58,.12,.98) .4s;
        animation: tipScale cubic-bezier(.22,.58,.12,.98) .4s;
      }
    .send-wrap{
      margin-top: 20px;
    }
    .input{
        width: 300px;
        height: 30px;
        line-height: 30px;
        outline: none;
        -webkit-appearance: none;
        border-radius: 5px;
        padding:0;
        padding-left: 10px;
    }
    .send-btn{
        height: 38px;
        line-height: 38px;
        text-align: center;
        font-weight: bold;
        color: #fff;
        background: #93d0ea;
        text-shadow:1px 1px 1px #333;
        border-radius: 5px;
        margin:0 20px 20px 0;
        position: relative;
        overflow: hidden;
        cursor: pointer;
        padding:5px 15px;
    }
    @-webkit-keyframes tipScale{
        0{
          -webkit-transform: scale(0);
           transform: scale(0);
        }
        50% {
           -webkit-transform: scale(1.1);
           transform: scale(1.1);
        }
        100% {
          -webkit-transform: scale(1);
          transform: scale(1);
        }
    }
  </style>
</head>
<body>
<div class="box">
   <div class="barrage-container-wrap clearfix">
    <div class="barrage-container">
    </div> 
  </div>
  <div class="send-wrap">
    <input type="text" class="input" placeholder="弹幕发送">
    <span class="send-btn">发送</span>
  </div> 
</div>
</body>
<script>
;(function(){
  var barrageArray = [
          {
            url: '用户头像',
            text: '秋天爱美丽',
            level: 10
          },
          {
            url: '用户头像',
            text: '今天很开心啊',
            level: 10
          },
          {
            url: '用户头像',
            text: 'winter has come',
            level: 10
          },
          {
            url: '',
            text: '土耳其现在形势',
            level: 10
          },
          {
            url: '',
            text: '没事早点回家吃饭啊',
            level: 10
          },
           {
            url: '',
            text: '这主角真实醉了,不会回啊',
            level: 10
          },
          {
            url: '',
            text: '背景音乐真好听啊',
            level: 10
          },
          {
            url: '',
            text: '背景音乐是***',
            level: 10
          },
          {
            url: '',
            text: '经费在燃烧啊',
            level: 10
          },
          {
            url: '',
            text: '国产良心剧',
            level: 10
          },
      ];
  var barrageColorArray = [
    '#0099CC','#333333', '#009966','#FFFF66','#9933FF','#FFFF99','#CCCCFF','#CC9933','#FFFF66'
  ];
  var barrageTipWidth = 50; //提示语的长度
 
  var barrageBoxWrap = document.querySelector('.barrage-container-wrap');;
  var barrageBox = document.querySelector('.barrage-container');
  var inputBox = document.querySelector('.input');
  var sendBtn = document.querySelector('.send-btn');
 
  //容器的宽高度
  var barrageWidth = ~~window.getComputedStyle(barrageBoxWrap).width.replace('px','');
  var barrageHeight = ~~window.getComputedStyle(barrageBoxWrap).height.replace('px','');
 
  //发送
  function sendMsg(){
    var inputValue = inputBox.value;
    inputValue .replace(/\ +/g, "");
 
    if (inputValue.length <= 0) {
        alert('请输入');
        return false;
    }
 
    //生成弹幕
    createBarrage(inputValue,true);
    inputBox.value = ''; 
  }
   
 
  //创建弹幕
  function createBarrage(msg, isSendMsg){
    var divNode = document.createElement('div');
    var spanNode = document.createElement('span');
 
    divNode.innerHTML = msg;
    divNode.classList.add('barrage-item');
    barrageBox.appendChild(divNode);
 
    spanNode.innerHTML = '举报';
    spanNode.classList.add('barrage-tip');
    divNode.appendChild(spanNode);
 
    barrageOffsetLeft = getRandom(barrageWidth, barrageWidth*2);
    barrageOffsetLeft = isSendMsg ? barrageWidth : barrageOffsetLeft
    barrageOffsetTop = getRandom(10, barrageHeight-10);
    barrageColor = barrageColorArray[Math.floor(Math.random()*(barrageColorArray.length))];
 
    //执行初始化滚动
    initBarrage.call(divNode,{
      left : barrageOffsetLeft,
      top : barrageOffsetTop,
      color : barrageColor
    });
  }
 
  //初始化弹幕移动(速度,延迟)
  function initBarrage(obj) {
    //初始化
    obj.top = obj.top || 0;
    obj.class = obj.color || '#fff';
    this.style.left = obj.left + 'px';
    this.style.top = obj.top + 'px';
    this.style.color = obj.color;  
 
    //添加属性
    this.distance = 0;
    this.width = ~~window.getComputedStyle(this).width.replace('px','');
    this.offsetLeft = obj.left;
    this.timer = null;
 
    //弹幕子节点
    var barrageChileNode = this.children[0];
    barrageChileNode.style.left = (this.width-barrageTipWidth)/2 + 'px';
 
    //运动
    barrageAnimate(this);
 
    //停止
    this.onmouseenter = function(){
      barrageChileNode.style.display= 'block';
      cancelAnimationFrame(this.timer);
    };
 
    this.onmouseleave = function(){
      barrageChileNode.style.display = 'none';
      barrageAnimate(this);
    };
 
    //举报
    barrageChileNode.onclick = function(){
      alert('举报成功');
    }
  }
  
  //弹幕动画
  function barrageAnimate(obj){
    move(obj);
 
    if(Math.abs(obj.distance) < obj.width+obj.offsetLeft){
      obj.timer = requestAnimationFrame(function(){
        barrageAnimate(obj);
      });
    }else{
      cancelAnimationFrame(obj.timer);
      //删除节点
      obj.parentNode.removeChild(obj);
    }
  }
 
  //移动
  function move(obj){
    obj.distance--;
    obj.style.transform = 'translateX('+obj.distance+'px)';
    obj.style.webkitTransform = 'translateX('+obj.distance+'px)';
  }
 
  //随机获取高度
  function getRandom(start, end){
    return start +(Math.random() * (end - start));
  }
 
 
  /*******初始化事件**********/
  //系统数据
  barrageArray.forEach(function(item,index){
    createBarrage(item.text, false);
  });
 
  //点击发送
  sendBtn.onclick = sendMsg;   //点击发送
 
  //回车
  inputBox.onkeydown = function(e){
    e = e|| window.event;
    if(e.keyCode == 13){
      send();
    }
  }
 
})()
 
//兼容写法
(function() {
    var lastTime = 0;
    var vendors = ['webkit', 'moz'];
    for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
        window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
        window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] ||    // Webkit中此取消方法的名字变了
                                      window[vendors[x] + 'CancelRequestAnimationFrame'];
    }
 
    if (!window.requestAnimationFrame) {
        window.requestAnimationFrame = function(callback, element) {
            var currTime = new Date().getTime();
            var timeToCall = Math.max(0, 16.7 - (currTime - lastTime));
            var id = window.setTimeout(function() {
                callback(currTime + timeToCall);
            }, timeToCall);
            lastTime = currTime + timeToCall;
            return id;
        };
    }
    if (!window.cancelAnimationFrame) {
        window.cancelAnimationFrame = function(id) {
            clearTimeout(id);
        };
    }
}());
</script>
</html>

  • 23
    点赞
  • 64
    收藏
    觉得还不错? 一键收藏
  • 15
    评论
要在 Android 应用中实现幕,可以考虑使用 SurfaceView 和 Canvas 组合来实现。 具体步骤如下: 1. 创建一个 SurfaceView,并在其上绘制幕。可以通过 SurfaceHolder 获取 Canvas 对象,然后在 Canvas 上绘制文本。 2. 创建一个幕控制器,用于控制幕的生成和绘制。幕控制器需要维护一个幕列表,每隔一定时间生成新的幕并添加到列表中。 3. 在 SurfaceView 的回调方法中,绘制幕列表中的所有幕,并更新幕的位置。 4. 可以根据需要添加一些特效,比如透明度渐变、移动速度变化等。 示例代码如下: ```java public class DanmakuSurfaceView extends SurfaceView implements SurfaceHolder.Callback { private SurfaceHolder surfaceHolder; private DanmakuController danmakuController; public DanmakuSurfaceView(Context context) { super(context); init(); } public DanmakuSurfaceView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public DanmakuSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { surfaceHolder = getHolder(); surfaceHolder.addCallback(this); danmakuController = new DanmakuController(); } @Override public void surfaceCreated(SurfaceHolder holder) { danmakuController.start(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { danmakuController.stop(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); List<Danmaku> danmakus = danmakuController.getDanmakus(); for (Danmaku danmaku : danmakus) { Paint paint = new Paint(); paint.setTextSize(danmaku.getTextSize()); paint.setColor(danmaku.getTextColor()); paint.setAlpha(danmaku.getAlpha()); canvas.drawText(danmaku.getText(), danmaku.getX(), danmaku.getY(), paint); } } } ``` 幕控制器代码示例: ```java public class DanmakuController implements Runnable { private boolean isRunning; private List<Danmaku> danmakus; public DanmakuController() { danmakus = new ArrayList<>(); } public void start() { isRunning = true; new Thread(this).start(); } public void stop() { isRunning = false; } public List<Danmaku> getDanmakus() { return danmakus; } @Override public void run() { while (isRunning) { // 生成新的幕并添加到列表中 Danmaku danmaku = new Danmaku(); danmakus.add(danmaku); // 更新幕位置 for (Danmaku d : danmakus) { d.move(); } // 每隔一定时间刷新界面 try { Thread.sleep(16); } catch (InterruptedException e) { e.printStackTrace(); } } } } ``` 幕类代码示例: ```java public class Danmaku { private static final int SPEED = 10; // 幕速度 private static final int MAX_ALPHA = 255; // 最大透明度 private String text; // 幕文本 private int textColor; // 幕颜色 private int textSize; // 幕字体大小 private int x; // 幕横坐标 private int y; // 幕纵坐标 private int alpha; // 幕透明度 public Danmaku() { text = "Hello, world!"; textColor = Color.WHITE; textSize = 36; x = 0; y = 0; alpha = MAX_ALPHA; } public String getText() { return text; } public void setText(String text) { this.text = text; } public int getTextColor() { return textColor; } public void setTextColor(int textColor) { this.textColor = textColor; } public int getTextSize() { return textSize; } public void setTextSize(int textSize) { this.textSize = textSize; } public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } public int getAlpha() { return alpha; } public void setAlpha(int alpha) { this.alpha = alpha; } public void move() { x += SPEED; alpha -= 5; if (alpha < 0) { alpha = 0; } } } ``` 这只是一个简单的示例,实际开发中需要根据具体需求进行调整。
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值