ybbarrage.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. (function() {
  2. 'use strict'
  3. function YBBarrage ({container, barrages = [], config = {}}){
  4. if(!(this instanceof YBBarrage)){ //如果this不是指向MyClass
  5. throw new TypeError("TypeError: Class constructor YBBarrage cannot be invoked without 'new'")
  6. }
  7. this.container = container
  8. this.canvas = null
  9. this.paused = true
  10. this.barrages = barrages
  11. this.config = {}
  12. this._ctx = null
  13. this._barrages = []
  14. this._w = 0
  15. this._h = 0
  16. this._drawTimer = null
  17. this._drawAnima = null
  18. this.currentTime = 0
  19. this._count = 0
  20. this._startTime = 0
  21. this._refreshTimer = null
  22. this.setConfig(config)
  23. this._init()
  24. }
  25. Object.defineProperty(YBBarrage.prototype,'_init',{
  26. value:function(){
  27. if(!(this instanceof YBBarrage)){//那么相反 不是正常调用的就是错误的调用
  28. throw new TypeError("TypeError: YBBarrage._init is not a constructor")
  29. }
  30. this.canvas = document.createElement('CANVAS');
  31. this.canvas.setAttribute('width', this.container.offsetWidth);
  32. this.canvas.setAttribute('height', this.container.offsetHeight);
  33. this.container.appendChild(this.canvas);
  34. this._ctx = this.canvas.getContext('2d');
  35. let rect = this.canvas.getBoundingClientRect();
  36. this._w = rect.right - rect.left;
  37. this._h = rect.bottom - rect.top;
  38. this.currentTime = this.config.initialTime;
  39. window.addEventListener('resize', this.refresh.bind(this))
  40. },
  41. enumerable:false
  42. })
  43. Object.defineProperty(YBBarrage.prototype,'_destroy',{
  44. value:function(){
  45. if(!(this instanceof YBBarrage)){//那么相反 不是正常调用的就是错误的调用
  46. throw new TypeError("TypeError: YBBarrage._destroy is not a constructor")
  47. }
  48. this.paused = true;
  49. this._clearTimer();
  50. this._cancelAnima();
  51. this._clear();
  52. this.barrages = [];
  53. this._barrages = [];
  54. this.container = null;
  55. this._ctx = null;
  56. if ( this.canvas ) {
  57. this.canvas.remove();
  58. this.canvas = null;
  59. }
  60. window.removeEventListener('resize', this.refresh.bind(this))
  61. },
  62. enumerable:false
  63. })
  64. //开始绘制
  65. Object.defineProperty(YBBarrage.prototype,'_render',{
  66. value:function(){
  67. if(!(this instanceof YBBarrage)){//那么相反 不是正常调用的就是错误的调用
  68. throw new TypeError("TypeError: YBBarrage._render is not a constructor")
  69. }
  70. this._cancelAnima();
  71. if ( this.paused ) return;
  72. if (this._barrages.length) {
  73. this._clear();
  74. for (let i = 0; i < this._barrages.length; i++) {
  75. let b = this._barrages[i];
  76. if (b.left + b.width <= 0) {
  77. this._barrages.splice(i, 1);
  78. i--;
  79. continue;
  80. }
  81. b.offset = this._detectionBump(b);
  82. let offset = b.offset * this.config.playbackRate;
  83. b.left -= offset;
  84. this._drawText(b);
  85. }
  86. }
  87. this._drawAnima = window.requestAnimationFrame(this._render.bind(this));
  88. },
  89. enumerable:false
  90. })
  91. Object.defineProperty(YBBarrage.prototype,'_cancelAnima',{
  92. value:function(){
  93. if(!(this instanceof YBBarrage)){//那么相反 不是正常调用的就是错误的调用
  94. throw new TypeError("TypeError: YBBarrage._cancelAnima is not a constructor")
  95. }
  96. if ( this._drawAnima ) {
  97. window.cancelAnimationFrame(this._drawAnima)
  98. this._drawAnima = null
  99. }
  100. },
  101. enumerable:false
  102. })
  103. Object.defineProperty(YBBarrage.prototype,'_clear',{
  104. value:function(){
  105. if(!(this instanceof YBBarrage)){//那么相反 不是正常调用的就是错误的调用
  106. throw new TypeError("TypeError: YBBarrage._clear is not a constructor")
  107. }
  108. this._ctx && this._ctx.clearRect(0, 0, this._w, this._h);
  109. },
  110. enumerable:false
  111. })
  112. //绘制文字
  113. Object.defineProperty(YBBarrage.prototype,'_drawText',{
  114. value:function(barrage){
  115. if(!(this instanceof YBBarrage)){//那么相反 不是正常调用的就是错误的调用
  116. throw new TypeError("TypeError: YBBarrage._drawText is not a constructor")
  117. }
  118. if ( this._ctx ) {
  119. this._ctx.beginPath();
  120. this._ctx.font = `${barrage.fontSize || this.config.fontSize}px ${this.config.fontFamily}`;
  121. this._ctx.strokeStyle = this._hex2rgba(this._getStrokeColor(barrage.color || this.config.defaultColor), this.config.opacity);
  122. this._ctx.strokeText(barrage.text, barrage.left, barrage.top);
  123. this._ctx.fillStyle = this._hex2rgba(barrage.color || this.config.defaultColor, this.config.opacity);
  124. this._ctx.fillText(barrage.text, barrage.left, barrage.top);
  125. this._ctx.closePath();
  126. }
  127. },
  128. enumerable:false
  129. })
  130. Object.defineProperty(YBBarrage.prototype,'_startTimer',{
  131. value:function(){
  132. if(!(this instanceof YBBarrage)){//那么相反 不是正常调用的就是错误的调用
  133. throw new TypeError("TypeError: YBBarrage._startTimer is not a constructor")
  134. }
  135. this._startTime = new Date().getTime();
  136. this._count = 0;
  137. this._drawTimer = window.setTimeout(this._fixed.bind(this), 1000);
  138. },
  139. enumerable:false
  140. })
  141. Object.defineProperty(YBBarrage.prototype,'_clearTimer',{
  142. value:function(){
  143. if(!(this instanceof YBBarrage)){//那么相反 不是正常调用的就是错误的调用
  144. throw new TypeError("TypeError: YBBarrage._clearTimer is not a constructor")
  145. }
  146. if ( this._drawTimer ) {
  147. window.clearTimeout(this._drawTimer)
  148. this._drawTimer = null
  149. }
  150. },
  151. enumerable:false
  152. })
  153. Object.defineProperty(YBBarrage.prototype,'_fixed',{
  154. value:function(){
  155. if(!(this instanceof YBBarrage)){//那么相反 不是正常调用的就是错误的调用
  156. throw new TypeError("TypeError: YBBarrage._fixed is not a constructor")
  157. }
  158. this._clearTimer();
  159. if ( this.paused ) return;
  160. if ( this.config.duration > -1 && this.currentTime >= this.config.duration ) {
  161. this.seek(0);
  162. return;
  163. }
  164. this._count++;
  165. this.currentTime += (1 * this.config.playbackRate);
  166. let barrages = this.barrages.filter(barrage => parseInt(barrage.time) == parseInt(this.currentTime));
  167. for ( let i = 0; i < barrages.length; i++ ) {
  168. let bar = this._getBarrage(barrages[i]);
  169. bar && this._barrages.push(bar);
  170. }
  171. let offset = new Date().getTime() - (this._startTime + (this._count * 1000));
  172. let nextTime = 1000 - offset;
  173. if (nextTime < 0) nextTime = 0;
  174. this._drawTimer = window.setTimeout(this._fixed.bind(this), nextTime);
  175. },
  176. enumerable:false
  177. })
  178. //测绘弹幕
  179. Object.defineProperty(YBBarrage.prototype,'_getBarrage',{
  180. value:function(barrage, isVisible = false){
  181. if(!(this instanceof YBBarrage)){//那么相反 不是正常调用的就是错误的调用
  182. throw new TypeError("TypeError: YBBarrage._getBarrage is not a constructor")
  183. }
  184. let fontSize = barrage.fontSize || this.config.fontSize;
  185. let offset = this._getOffset();
  186. this._ctx.font = `${barrage.fontSize || this.config.fontSize}px ${this.config.fontFamily}`;
  187. let width = Math.ceil(this._ctx.measureText(barrage.text).width);
  188. let top = this._getTop(fontSize, width, offset, isVisible);
  189. if ( top > -1 ) {
  190. return {
  191. text: barrage.text,
  192. time: barrage.time,
  193. fontSize: fontSize,
  194. color: barrage.color || this.config.defaultColor,
  195. top: top,
  196. left: this._w,
  197. offset: offset,
  198. width: width
  199. }
  200. }
  201. return false;
  202. },
  203. enumerable:false
  204. })
  205. Object.defineProperty(YBBarrage.prototype,'_getStrokeColor',{
  206. value:function(color){
  207. if(!(this instanceof YBBarrage)){//那么相反 不是正常调用的就是错误的调用
  208. throw new TypeError("TypeError: YBBarrage._getStrokeColor is not a constructor")
  209. }
  210. let hex = color.length == 7 ? color : '#' + color.slice(1, 4) + color.slice(1, 4)
  211. const r = parseInt(hex.slice(1,3),16);
  212. const g = parseInt(hex.slice(3,5),16);
  213. const b = parseInt(hex.slice(5,7),16);
  214. let $grayLevel = (r * 0.299) + (g * 0.587) + (b * 0.144);
  215. if ($grayLevel >= 120) {
  216. return '#000000';
  217. } else {
  218.   return '#ffffff';
  219. }
  220. },
  221. enumerable:false
  222. })
  223. Object.defineProperty(YBBarrage.prototype,'_hex2rgba',{
  224. value:function(hex, opacity){
  225. if(!(this instanceof YBBarrage)){//那么相反 不是正常调用的就是错误的调用
  226. throw new TypeError("TypeError: YBBarrage._hex2rgba is not a constructor")
  227. }
  228. hex = hex.length == 7 ? hex : '#' + hex.slice(1, 4) + hex.slice(1, 4)
  229. let str="rgba("
  230. const r = parseInt(hex.slice(1,3),16).toString();
  231. const g = parseInt(hex.slice(3,5),16).toString();
  232. const b = parseInt(hex.slice(5,7),16).toString();
  233. str += r+","+g+","+b+","+opacity+")";
  234. return str;
  235. },
  236. enumerable:false
  237. })
  238. //计算弹幕距离顶部位置
  239. Object.defineProperty(YBBarrage.prototype,'_getTop',{
  240. value:function(size, width, offset, isVisible = false){
  241. if(!(this instanceof YBBarrage)){//那么相反 不是正常调用的就是错误的调用
  242. throw new TypeError("TypeError: YBBarrage._getTop is not a constructor")
  243. }
  244. //canvas绘制文字x,y坐标是按文字左下角计算,预留30px
  245. let top = -1;
  246. let len = -1
  247. //根据弹幕高度分割轨道
  248. for ( let i = 0; i < Math.floor(this._h / (size + this.config.lineHeight)); i++ ) {
  249. //当前轨道的顶部位置
  250. let nowTop = ((i + 1) * size) + (i * this.config.lineHeight);
  251. //当前轨道上有多少条弹幕
  252. let barrages = this._barrages.filter(barrage => barrage.top < nowTop + size && barrage.fontSize + barrage.top > nowTop );
  253. if ( barrages.length > 0 ) {
  254. //当前轨道有弹幕运行
  255. let arr = barrages.map(barrage => barrage.left + barrage.width);
  256. if ( !this.config.overlap || isVisible ) {
  257. //开启防重叠会取消多余弹幕显示
  258. if ( arr.length < len || len < 0 ) {
  259. len = arr.length;
  260. top = nowTop;
  261. }
  262. }
  263. //获取当前轨道最右的弹幕
  264. let max = Math.max(...arr);
  265. //如果当前轨道还有空位则将弹幕放入当前轨道
  266. if ( max < this._w - (isVisible ? width : 10) ) {
  267. top = nowTop;
  268. break;
  269. }
  270. } else {
  271. //当前轨道没有弹幕运行
  272. top = nowTop;
  273. break;
  274. }
  275. }
  276. return top;
  277. },
  278. enumerable:false
  279. })
  280. //获取偏移量
  281. Object.defineProperty(YBBarrage.prototype,'_getOffset',{
  282. value:function(){
  283. if(!(this instanceof YBBarrage)){//那么相反 不是正常调用的就是错误的调用
  284. throw new TypeError("TypeError: YBBarrage._getOffset is not a constructor")
  285. }
  286. return parseFloat( ((this.config.speed / 70) + Math.random()).toFixed(1) );
  287. },
  288. enumerable:false
  289. })
  290. //碰撞检测 速度计算
  291. Object.defineProperty(YBBarrage.prototype,'_detectionBump',{
  292. value:function(bar){
  293. if(!(this instanceof YBBarrage)){//那么相反 不是正常调用的就是错误的调用
  294. throw new TypeError("TypeError: YBBarrage._detectionBump is not a constructor")
  295. }
  296. let nowTop = bar.top;
  297. let offset = bar.offset;
  298. //检测当前弹幕轨道有多少条弹幕
  299. let barrages = this._barrages.filter(barrage => barrage.top < nowTop + bar.fontSize && barrage.fontSize + barrage.top > nowTop && bar.left != barrage.left);
  300. //检测当前弹幕轨道前方有多少条弹幕
  301. let headbarrages = barrages.filter(barrage => bar.left + bar.width >= barrage.left + barrage.width + 10 && barrage.left + barrage.width > 0);
  302. //检测当前弹幕轨道后方有多少条弹幕
  303. let footbarrages = barrages.filter(barrage => bar.left + bar.width + 10 < barrage.left && barrage.left + barrage.width > 0);
  304. if ( headbarrages.length > 0 ) {
  305. let arr = headbarrages.map(barrage => barrage.left + barrage.width);
  306. //取出离当前弹幕最近的那条弹幕
  307. let max = Math.max(...arr);
  308. let i = arr.indexOf(max);
  309. if ( bar.left > max + 20 ) {
  310. //如果离最近弹幕距离大于10,则加速
  311. offset = headbarrages[i].offset + 0.01;
  312. } else {
  313. //否则减速
  314. offset = headbarrages[i].offset - 0.01;
  315. }
  316. }
  317. if ( footbarrages.length > 0 ) {
  318. //前方一条弹幕都没有 且 未弹幕未完全显示 则加速
  319. if ( bar.left + bar.width > this._w && headbarrages.length == 0 ) {
  320. offset = bar.offset + 0.01;
  321. } else {
  322. let arr = footbarrages.map(barrage => barrage.left);
  323. //取出离当前弹幕最近的那条弹幕
  324. let min = Math.min(...arr);
  325. let i = arr.indexOf(min);
  326. //如果后方存在弹幕 且距离小于10则加速
  327. if ( min - (bar.left + bar.width) <= 20 ) {
  328. offset = bar.offset + 0.01;
  329. }
  330. }
  331. }
  332. if ( offset < 0.5 ) offset = 0.5;
  333. if ( offset > 3 ) offset = 3;
  334. return offset;
  335. },
  336. enumerable:false
  337. })
  338. Object.defineProperty(YBBarrage.prototype,'setBarrages',{
  339. value:function(barrages){
  340. if(!(this instanceof YBBarrage)){//那么相反 不是正常调用的就是错误的调用
  341. throw new TypeError("TypeError: YBBarrage.setBarrages is not a constructor")
  342. }
  343. this.barrages = barrages
  344. },
  345. enumerable:false
  346. })
  347. Object.defineProperty(YBBarrage.prototype,'add',{
  348. value:function(barrage){
  349. if(!(this instanceof YBBarrage)){//那么相反 不是正常调用的就是错误的调用
  350. throw new TypeError("TypeError: YBBarrage.add is not a constructor")
  351. }
  352. const arr = Object.prototype.toString.call(barrage) == '[object Array]' ? barrage : [barrage]
  353. arr.forEach(item => {
  354. item.time = item.time || this.currentTime;
  355. this.barrages.push(item);
  356. if ( parseInt(item.time) == parseInt(this.currentTime) ) {
  357. const bar = this._getBarrage(item, true);
  358. bar.left = this._w - bar.width;
  359. this._barrages.push(bar);
  360. }
  361. })
  362. },
  363. enumerable:false
  364. })
  365. Object.defineProperty(YBBarrage.prototype,'setConfig',{
  366. value:function(config){
  367. if(!(this instanceof YBBarrage)){//那么相反 不是正常调用的就是错误的调用
  368. throw new TypeError("TypeError: YBBarrage.setConfig is not a constructor")
  369. }
  370. this.config = Object.assign({}, {
  371. duration: -1, // 弹幕动画的循环周期,-1 表示不循环播放
  372. speed: 150, // 弹幕的运动速度
  373. fontSize: 24, // 文字大小,单位:像素
  374. fontFamily: 'Microsoft Yahei', // 字体,默认值:微软雅黑
  375. opacity: 1, // 透明度,有效值 0-1
  376. defaultColor: '#fff', // 默认颜色,与 CSS 颜色属性一致
  377. lineHeight: 5,//行间距
  378. overlap: true,//开启防重叠 可能会导致部分弹幕不显示
  379. playbackRate: 1.0,//播放倍速
  380. initialTime: 0//初始时间
  381. }, this.config, config)
  382. },
  383. enumerable:false
  384. })
  385. //刷新尺寸(当容器大小变化时调用此接口)
  386. Object.defineProperty(YBBarrage.prototype,'refresh',{
  387. value:function(){
  388. if(!(this instanceof YBBarrage)){//那么相反 不是正常调用的就是错误的调用
  389. throw new TypeError("TypeError: YBBarrage.refresh is not a constructor")
  390. }
  391. if ( this._refreshTimer ) {
  392. window.clearTimeout(this._refreshTimer)
  393. this._refreshTimer = null
  394. }
  395. this._refreshTimer = window.setTimeout(() => {
  396. if ( this.canvas && this.container ) {
  397. this.canvas.width = this.container.offsetWidth;
  398. this.canvas.height = this.container.offsetHeight;
  399. this._w = this.container.offsetWidth;
  400. this._h = this.container.offsetHeight;
  401. let paused = this.paused;
  402. if ( !paused ) {
  403. this.play()
  404. }
  405. window.clearTimeout(this._refreshTimer)
  406. this._refreshTimer = null
  407. }
  408. }, 200)
  409. },
  410. enumerable:false
  411. })
  412. Object.defineProperty(YBBarrage.prototype,'play',{
  413. value:function(){
  414. if(!(this instanceof YBBarrage)){//那么相反 不是正常调用的就是错误的调用
  415. throw new TypeError("TypeError: YBBarrage.play is not a constructor")
  416. }
  417. if ( this.paused ) {
  418. this.paused = false;
  419. this._startTimer();
  420. this._render();
  421. }
  422. },
  423. enumerable:false
  424. })
  425. Object.defineProperty(YBBarrage.prototype,'pause',{
  426. value:function(){
  427. if(!(this instanceof YBBarrage)){//那么相反 不是正常调用的就是错误的调用
  428. throw new TypeError("TypeError: YBBarrage.pause is not a constructor")
  429. }
  430. if ( !this.paused ) {
  431. this.paused = true;
  432. this._clearTimer();
  433. this._cancelAnima();
  434. }
  435. },
  436. enumerable:false
  437. })
  438. Object.defineProperty(YBBarrage.prototype,'stop',{
  439. value:function(){
  440. if(!(this instanceof YBBarrage)){//那么相反 不是正常调用的就是错误的调用
  441. throw new TypeError("TypeError: YBBarrage.stop is not a constructor")
  442. }
  443. this._destroy();
  444. },
  445. enumerable:false
  446. })
  447. Object.defineProperty(YBBarrage.prototype,'seek',{
  448. value:function(time){
  449. if(!(this instanceof YBBarrage)){//那么相反 不是正常调用的就是错误的调用
  450. throw new TypeError("TypeError: YBBarrage.seek is not a constructor")
  451. }
  452. this._barrages = [];
  453. this.currentTime = parseInt(time);
  454. this._clear();
  455. let paused = this.paused;
  456. if ( !paused ) {
  457. this.play()
  458. }
  459. },
  460. enumerable:false
  461. })
  462. if ("object" == typeof exports && "undefined" != typeof module) module.exports = YBBarrage;
  463. else if ("function" == typeof define && define.amd) define([], YBBarrage);
  464. else {
  465. var t;
  466. t = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof self ? self : this, t.YBBarrage = YBBarrage
  467. }
  468. })();