今天看啥  ›  专栏  ›  任侠

弹幕效果实现以及用到的设计模式

任侠  · 掘金  ·  · 2017-12-10 09:05

文章预览

最近项目做了一个类似弹幕效果的功能,做之前看了一遍《Javascript设计模式与开发实践》,做完之后再去看代码,发现原来这些就是设计模式。

所谓的弹幕效果是这样的,普通弹幕和带图片弹幕从右边向右边移动。
效果

效果

最初的想法是用 Canvas,但实现到一半的时候考虑再三还是决定改用 DOM 节点来实现,主要考虑是,Canvas 绘制 background,border-radius 以及加载图片,点击事件都比较麻烦。

我做弹幕效果的整体思路:

思路

思路

控制器是调和关系和事件派发的中心,在 Raf 的每一帧去遍历跑道,找到 idle 状态的跑道,并且去内容中心获取内容,根据内容类型生成对应的载体子类,根据跑道初始化子类,并将该跑道置为 busy。

这个需求里面弹幕分为两种类型,文本弹幕和图片弹幕,这两种弹幕都继承自一个载体父类 Carrier,这个父类拥有初始化,边界判断,事件监听等方法,子类就只需要负责各自的 init 和 render 方法。

弹幕从起跑线开始出发,整体经过起跑线时触发 start_off 事件,并通知所有该事件监听者(现在的只有跑道),告诉跑道可以把状态改为空闲了。

弹幕整体经过终点时,触发 die 事件,再这个事件里面通知载体池将自己回收,把内容抛回给内容中心,等待再次被翻牌。

载体池是个载体生成工厂,根据处理器需要的类型返回对应的弹幕,返回的弹幕有两种来源,一种是通过 new 出来,一种是通过 recycle 。

拥有上帝视角的人看到的是这样的:
上帝视角

上帝视角

用到的设计模式

工厂模式和享元模式

载体都是在载体池被创建出来的,但是这里的载体池不关心创建的是什么,只是根据需要的类型和对应的构造器进行生产,对应关系是在载体池初始化的时候传入。这样做的好处是不需要把类型判断和构造函数放到控制器中。外界不需要知道怎么生产,想要什么我给你什么。

lerp (value1, value2, amount) {
  amount = amount < 0 ? 0 : amount
  amount = amount > 1 ? 1 : amount
  return value1 + (value2 - value1) * amount
}

载体池除了创建还有回收功能,因为 DOM 节点代价比较昂贵,我们不希望每一条独立的弹幕都是一个新的 DOM,所以载体池还会对生成弹幕进行共享,避免多余的 DOM 节点生成。

lerp (value1, value2, amount) {
  amount = amount < 0 ? 0 : amount
  amount = amount > 1 ? 1 : amount
  return value1 + (value2 - value1) * amount
}

观察者模式

载体父类实现了简单的观察者模式,once 注册事件,notify 的时候清除所有监听,在每一帧的边缘检测里面去触发事件,通知监听者(这里都是 callback)

lerp (value1, value2, amount) {
  amount = amount < 0 ? 0 : amount
  amount = amount > 1 ? 1 : amount
  return value1 + (value2 - value1) * amount
}

代理模式

由于使用的是 DOM 的方式,并且实现的是匀速弹幕效果,为了性能更好,我没有把 translate3d 的偏移放到了容器上来做,弹幕只在初始化的时候设置自己相对容器中的偏移值,这不算特别明显地代理模式~

代理模式是对于自身不方便处理的事务交给第三方处理,比如这里的点击事件,需求文档里面描述图片弹幕需要能点击查看大图,如果所有弹幕都绑定 click 事件,那么在弹幕被回收或者被移除的时候需要对事件进行解绑或替换。这时候可以考虑使用事件代理,只在容器上添加点击事件绑定,在点击事件回调里面根据 event.target 判断点中的弹幕。

lerp (value1, value2, amount) {
  amount = amount < 0 ? 0 : amount
  amount = amount > 1 ? 1 : amount
  return value1 + (value2 - value1) * amount
}

其他功能

支持拖拽

这是未雨绸缪,预料到老板会要求加拖拽弹幕的需求,提前实现,实现方法是容器监听 touch 事件,start 的时候 pause 原有定时器并启用 draggingTick ;move 事件里面移动容器,并执行原有定时器里面的 update 方法,这样弹幕事件该触发还是会触发;end 事件里面停掉 draggingTick resume 原来的定时器。
为了让拖拽效果不要太生硬,这里用了 lerp 大法:

lerp (value1, value2, amount) {
  amount = amount < 0 ? 0 : amount
  amount = amount > 1 ? 1 : amount
  return value1 + (value2 - value1) * amount
}

拖拽效果

拖拽效果

如需转载,请注明出处: w3ctrain.com/2017/12/10/…

我叫周晓楷

我现在是一名前端开发工程师,在编程的路上我还是个菜鸟,w3ctrain 是我用来记录学习和成长的地方。

………………………………

原文地址:访问原文地址
快照地址: 访问文章快照
总结与预览地址:访问总结与预览