jQuery.Danmu.js

弹幕jQuery.Danmu.js是一个让网页某div上运行弹幕效果的jQuery插件,具备彩色弹幕、顶端底端弹幕、自定义弹幕速度、实时调整透明度等弹幕功能,可以给video加上与之时间线同步的弹幕,也可以把弹幕放在网页的其他任何地方,只要你觉得酷炫。

This script should not be not be installed directly. It is a library for other scripts to include with the meta directive // @require https://update.greatest.deepsurf.us/scripts/24783/157449/jQueryDanmujs.js

  1. // ==UserScript==
  2. // @name jQuery.Danmu.js
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.1.0
  5. // @description 弹幕jQuery.Danmu.js是一个让网页某div上运行弹幕效果的jQuery插件,具备彩色弹幕、顶端底端弹幕、自定义弹幕速度、实时调整透明度等弹幕功能,可以给video加上与之时间线同步的弹幕,也可以把弹幕放在网页的其他任何地方,只要你觉得酷炫。
  6. // @author cyntax
  7. // @match https://www.douyu.com/*
  8. // @match http://www.douyu.com/*
  9. // @icon http://www.douyu.com/favicon.ico
  10. // @grant none
  11. // @copyrig h (c) 2011 Cyntax Technologies - http://huck
  12. // ==/UserScript==
  13.  
  14. /**
  15. * 专为danmuplayer定制的jquery.danmu.js
  16. *
  17. *
  18. * jQuery Generic Plugin Module
  19. * Version 0.1
  20. * Copyright (c) 2011 Cyntax Technologies - http://cyntaxtech.com
  21. * Licensed under the Cyntax Open Technology License
  22. * http://code.cyntax.com/licenses/cyntax-open-technology
  23. */
  24.  
  25. (function ($) {
  26. $.jQueryPlugin = function (name) {
  27. $.fn[name] = function (options) {
  28. var args = Array.prototype.slice.call(arguments, 1);
  29. if (this.length) {
  30. return this.each(function () {
  31. var instance = $.data(this, name) || $.data(this, name, new cyntax.plugins[name](this, options)._init());
  32. if (typeof options === "string") {
  33. options = options.replace(/^_/, "");
  34. if (instance[options]) {
  35. instance[options].apply(instance, args);
  36. }
  37. }
  38. });
  39. }
  40. };
  41. };
  42. })(jQuery);
  43.  
  44. var cyntax = {
  45. plugins: {}
  46. };
  47. ;
  48. /*!
  49. * Pause jQuery plugin v0.1
  50. *
  51. * Copyright 2010 by Tobia Conforto <tobia.conforto@gmail.com>
  52. *
  53. * Based on Pause-resume-animation jQuery plugin by Joe Weitzel
  54. *
  55. * This program is free software; you can redistribute it and/or modify it
  56. * under the terms of the GNU General Public License as published by the Free
  57. * Software Foundation; either version 2 of the License, or(at your option)
  58. * any later version.
  59. *
  60. * This program is distributed in the hope that it will be useful, but WITHOUT
  61. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  62. * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
  63. * more details.
  64. *
  65. * You should have received a copy of the GNU General Public License along with
  66. * this program; if not, write to the Free Software Foundation, Inc., 51
  67. * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  68. */
  69. /* Changelog:
  70. *
  71. * 0.1 2010-06-13 Initial release
  72. */
  73. (function () {
  74. var $ = jQuery,
  75. pauseId = 'jQuery.pause',
  76. uuid = 1,
  77. oldAnimate = $.fn.animate,
  78. anims = {};
  79.  
  80. function now() {
  81. return new Date().getTime();
  82. }
  83.  
  84. $.fn.animate = function (prop, speed, easing, callback) {
  85. var optall = $.speed(speed, easing, callback);
  86. optall.complete = optall.old; // unwrap callback
  87. return this.each(function () {
  88. // check pauseId
  89. if (!this[pauseId])
  90. this[pauseId] = uuid++;
  91. // start animation
  92. var opt = $.extend({}, optall);
  93. oldAnimate.apply($(this), [prop, $.extend({}, opt)]);
  94. // store data
  95. anims[this[pauseId]] = {
  96. run: true,
  97. prop: prop,
  98. opt: opt,
  99. start: now(),
  100. done: 0
  101. };
  102. });
  103. };
  104.  
  105. $.fn.pause = function () {
  106. return this.each(function () {
  107. // check pauseId
  108. if (!this[pauseId])
  109. this[pauseId] = uuid++;
  110. // fetch data
  111. var data = anims[this[pauseId]];
  112. if (data && data.run) {
  113. data.done += now() - data.start;
  114. if (data.done > data.opt.duration) {
  115. // remove stale entry
  116. delete anims[this[pauseId]];
  117. } else {
  118. // pause animation
  119. $(this).stop().stop().stop();
  120. $(this).stop();
  121. $(this).stop();
  122. $(this).stop();
  123. $(this).stop();
  124. $(this).stop();
  125. $(this).stop();
  126. $(this).stop();
  127. $(this).stop();
  128. $(this).stop();
  129. $(this).stop();
  130. $(this).stop();
  131. $(this).stop();
  132. $(this).stop();
  133. $(this).stop();
  134. data.run = false;
  135. }
  136. }
  137. });
  138. };
  139.  
  140. $.fn.resume = function () {
  141. return this.each(function () {
  142. // check pauseId
  143. if (!this[pauseId])
  144. this[pauseId] = uuid++;
  145. // fetch data
  146. var data = anims[this[pauseId]];
  147. if (data && !data.run) {
  148. // resume animation
  149. data.opt.duration -= data.done;
  150. data.done = 0;
  151. data.run = true;
  152. data.start = now();
  153. oldAnimate.apply($(this), [data.prop, $.extend({}, data.opt)]);
  154. }
  155. });
  156. };
  157. })();
  158. ;
  159. /**
  160. * jQuery Timer Plugin
  161. * Project page - http://code.cyntaxtech.com/plugins/jquery-timer
  162. * Version 0.1.1
  163. * Copyright (c) 2011 Cyntax Technologies - http://cyntaxtech.com
  164. * dependencies: jquery.plugin.js
  165. * Licensed under the Cyntax Open Technology License
  166. * http://code.cyntax.com/licenses/cyntax-open-technology
  167. * ------------------------------------
  168. * For details, please visit:
  169. * http://code.cyntaxtech.com/plugins/jquery-timer
  170. */
  171.  
  172. (function ($) {
  173. cyntax.plugins.timer = function (ele, options) {
  174. this.$this = $(ele);
  175. this.options = $.extend({}, this.defaults, options);
  176. this.timer_info = {id: null, index: null, state: 0};
  177. };
  178. cyntax.plugins.timer.prototype = {
  179. defaults: {
  180. delay: 1000, // delay in milliseconds (optional)
  181. repeat: false, // true to repeat the timer continuously, or a number for repeating this number of times (optional)
  182. autostart: true, // timer starts as soon as it is created, set false to start manually
  183. callback: null, // callback (optional)
  184. url: '', // url to load content from (optional)
  185. post: '' // post data (optional)
  186. },
  187. _init: function () {
  188. if (this.options.autostart) {
  189. this.timer_info.state = 1;
  190. this.timer_info.id = setTimeout($.proxy(this._timer_fn, this), this.options.delay);
  191. }
  192. return this;
  193. },
  194. _timer_fn: function () {
  195. if (typeof this.options.callback == "function")
  196. $.proxy(this.options.callback, this.$this).call(this, ++this.timer_info.index);
  197. else if (typeof this.options.url == "string") {
  198. ajax_options = {
  199. url: this.options.url,
  200. context: this,
  201. type: (typeof this.options.post == "string" && typeof this.options.post != "" == "" ? "POST" : "GET"),
  202. success: function (data, textStatus, jqXHR) {
  203. this.$this.html(data);
  204. }
  205. };
  206. if (typeof this.options.post == "string" && typeof this.options.post != "")
  207. ajax_options.data = this.options.post;
  208. $.ajax(ajax_options);
  209. }
  210. if (this.options.repeat && this.timer_info.state == 1 &&
  211. (typeof this.options.repeat == "boolean" || parseInt(this.options.repeat) > this.timer_info.index))
  212. this.timer_info.id = setTimeout($.proxy(this._timer_fn, this), this.options.delay);
  213. else
  214. this.timer_id = null;
  215. },
  216. start: function () {
  217. if (this.timer_info.state == 0) {
  218. this.timer_info.index = 0;
  219. this.timer_info.state = 1;
  220. this.timer_id = setTimeout($.proxy(this._timer_fn, this), this.options.delay);
  221. }
  222. },
  223.  
  224. stop: function () {
  225. if (this.timer_info.state == 1 && this.timer_info.id) {
  226. clearTimeout(this.timer_info.id);
  227. this.timer_id = null;
  228. }
  229. this.timer_info.state = 0;
  230. },
  231.  
  232. pause: function () {
  233. if (this.timer_info.state == 1 && this.timer_info.id)
  234. clearTimeout(this.timer_info.id);
  235. this.timer_info.state = 0;
  236. },
  237.  
  238. resume: function () {
  239. this.timer_info.state = 1;
  240. this.timer_id = setTimeout($.proxy(this._timer_fn, this), this.options.delay);
  241. }
  242. };
  243.  
  244. $.jQueryPlugin("timer");
  245.  
  246. })(jQuery);
  247. /*!
  248. *弹幕引擎核心
  249. *
  250. * Copyright 2015 by Ruiko Of AcGit.cc
  251. * @license MIT
  252. *
  253. * 版本3.0 2015/08/12
  254. */
  255.  
  256.  
  257. ;
  258. (function ($) {
  259. var Danmu = function (element, options) {
  260. this.$element = $(element);
  261. this.options = options;
  262. this.id = $(element).attr("id");
  263. $(element).data("nowTime", 0);
  264. $(element).data("danmuList", options.danmuList);
  265. $(element).data("opacity", options.opacity);
  266. $(element).data("paused", 1);
  267. $(element).data("topSpace", 0);
  268. $(element).data("bottomSpace", 0);
  269. this.$element.css({
  270. "position": "absolute",
  271. "left": this.options.left,
  272. "top": this.options.top,
  273. "width": this.options.width,
  274. "height": this.options.height,
  275. "z-index": this.options.zindex,
  276. "color": options.defaultFontColor,
  277. "overflow": "hidden"
  278. });
  279. var me = this;
  280. //播放器长宽
  281. me.height = this.$element.height();
  282. me.width = this.$element.width();
  283. //速度
  284. me.speed = 1000/options.speed;
  285.  
  286. //防止重复
  287. this.launched = [];
  288. this.preTime = 0;
  289. //最大弹幕数控制
  290. var maxCount = this.options.maxCountInScreen;
  291. var maxCountPerSec = this.options.maxCountPerSec;
  292. var nowCount = 0;
  293. var nowSecCount = 0;
  294. //格式控制
  295. this.rowCount = parseInt(me.height / options.FontSizeBig);
  296. if (me.options.SubtitleProtection) {
  297. me.rowCount = me.rowCount - 3;
  298. }
  299. this.rows = [];
  300. this.topRows=[];
  301. this.bottomRows=[];
  302. this.initRows = function (me) {
  303. // me.rowCount = parseInt(me.height / options.FontSizeBig);
  304. for (var i = 0; i < me.rowCount; i++) {
  305. me.rows[i] = 0;
  306. me.topRows[i]=0;
  307. me.bottomRows[i]=0;
  308. }
  309.  
  310. };
  311.  
  312. this.initRows(this);
  313. me.getRow = function (me) {
  314. var result = 0;
  315. while (me.rows[result] !== 0) {
  316. result = result + 1;
  317. if (result >= me.rowCount) {
  318.  
  319. me.initRows(me);
  320. result = 0;
  321. break;
  322. }
  323. }
  324. return result;
  325. };
  326. me.getTopRow = function (me) {
  327. for(var i=0;i<me.topRows.length;i++){
  328. if (me.topRows[i] == 0){
  329. return i;
  330. }
  331. }
  332. };
  333.  
  334. me.getBottomRow = function (me) {
  335. for(var i=0;i<me.bottomRows.length;i++){
  336. if (me.bottomRows[i] == 0){
  337. return i;
  338. }
  339. }
  340. };
  341. me.checkRow = function (me) {
  342. for (var i in me.rows) {
  343. if (me.rows[i] !== 0 && typeof($("#" + me.rows[i]).position()) !== "undefined" && ( $("#" + me.rows[i]).position().left < (me.$element.width() - $("#" + me.rows[i]).width()) )) {
  344. me.rows[i] = 0
  345. }
  346. }
  347. };
  348. //me.startCheck = function(me){
  349. // setInterval(me.checkRow(me),10);
  350. //};
  351. // me.startCheck(me);
  352.  
  353. $("<div class='danmakuTimer'></div>").appendTo(this.$element);
  354. this.$timer = $(".danmakuTimer");
  355. this.$timer.timer({
  356. delay: 100,
  357. repeat: options.sumTime,
  358. autostart: false,
  359. callback: function (index) {
  360. setTimeout(function () {
  361. //计时前置 试验表明前置很好
  362. if (me.options.danmuLoop && $(element).data("nowTime") >= $(element).data("sumTime")) {
  363. $(element).data("nowTime", 0);
  364. }
  365. $(element).data("nowTime", $(element).data("nowTime") + 1);
  366. //更新播放器面积参数
  367. me.height = $(element).height();
  368. me.width = $(element).width();
  369. //防止重复
  370. if (Math.abs($(element).data("nowTime") - (me.preTime + 1)) > 10) {
  371. me.launched = [];
  372. }
  373. me.preTime = $(element).data("nowTime");
  374. //更新行数
  375. var rowCOld = me.rowCount;
  376. me.rowCount = parseInt(me.height / options.FontSizeBig);
  377. setTimeout(me.checkRow(me), 0);
  378. //字幕保护
  379. if (me.options.SubtitleProtection) {
  380. me.rowCount = me.rowCount - 3;
  381. }
  382. if (rowCOld !== 0 && me.rowCount !== rowCOld) {
  383. me.initRows(me);
  384. }
  385. nowSecCount = 0;
  386.  
  387. if ($(element).data("danmuList")[$(element).data("nowTime")] && me.launched.indexOf($(element).data("nowTime")) < 0) {
  388. var nowTime = $(element).data("nowTime");
  389. var danmus = $(element).data("danmuList")[nowTime];
  390. for (var i = (danmus.length - 1); i >= 0; i--) {
  391. setTimeout(me.checkRow(me), 0);
  392. //setTimeout(me.runDanmu(danmus[i],nowCount,maxCount,nowSecCount,maxCountPerSec,options,me,$(element),speed,$(this)),1);
  393. // setTimeout(me.runDanmu(danmus[i],options,me,$(element),speed,$(this)),1);
  394.  
  395. // console.log(nowCount);
  396. var a_danmu = "<span class='danmaku' id='" + me.id + "tempDanmaku'></span>";
  397. $(element).append(a_danmu);
  398. var danmaku = danmus[i];
  399. $("#" + me.id + "tempDanmaku").text(danmaku.text)
  400. .css({
  401. "color": danmaku.color
  402. , "text-shadow": " 0px 0px 2px #000000"
  403. , "-moz-opacity": $(element).data("opacity")
  404. , "opacity": $(element).data("opacity")
  405. , "white-space": "nowrap"
  406. , "font-weight": "bold"
  407. , "font-family": "SimHei"
  408. , "font-size": options.FontSizeBig
  409. });
  410. if (danmaku.color < "#777777")
  411. $("#" + me.id + "tempDanmaku").css({
  412. "text-shadow": " 0px 0px 2px #FFFFFF"
  413. });
  414. if (danmaku.hasOwnProperty('isnew')) {
  415. $("#" + me.id + "tempDanmaku").css({"border": "2px solid " + danmaku.color});
  416. }
  417. if (danmaku.size == 0) $("#" + me.id + "tempDanmaku").css("font-size", options.fontSizeSmall);
  418. if (danmaku.position == 0) {
  419. var flyTmpName = me.id + "fly" + parseInt(new Date().getTime()).toString();
  420. $("#" + me.id + "tempDanmaku").attr("id", flyTmpName);
  421. if (nowCount <= maxCount && nowSecCount <= maxCountPerSec) {
  422. me.checkRow(me);
  423. var row = me.getRow(me);
  424. me.rows[row] = flyTmpName;
  425. danmaku["row"] = row;
  426. var top_local = (row) * options.FontSizeBig;
  427. danmaku["width"] = $("#" + flyTmpName).width();
  428. // var offsetLeft = parseInt(Math.random() * 2 * options.FontSizeBig);
  429. var left_local = $("#" + me.id).width();
  430. $("#" + flyTmpName).css({
  431. "width": $("#" + flyTmpName).width()
  432. , "position": "absolute"
  433. , "top": top_local
  434. , "left": left_local
  435. });
  436. var newSpeed = ($(element).width()+400)/me.speed;
  437. nowCount++;
  438. nowSecCount++;
  439. $("#" + flyTmpName).animate({left: -($("#" + flyTmpName).width() + 400)}, newSpeed
  440. , function () {
  441. $(this).remove();
  442. nowCount--;
  443. nowSecCount--;
  444. }
  445. );
  446. }
  447. else {
  448. $("#" + flyTmpName).remove();
  449. }
  450. }
  451. else if (danmaku.position == 1) {
  452. var topTmpId = me.id + "top" + parseInt(10000 * Math.random()).toString();
  453. $("#" + me.id + "tempDanmaku").attr("id", topTmpId);
  454. var temRow=me.getTopRow(me);
  455. $(element).data("topSpace", options.FontSizeBig*temRow);
  456. me.topRows[temRow]=1;
  457. $("#" + topTmpId).css({
  458. "width": "100%"
  459. , "text-align": "center"
  460. , "position": "absolute"
  461. , "top": ($(element).data("topSpace"))
  462. , "left": "0"
  463. });
  464. $("#" + topTmpId).data("row",temRow);
  465. $("#" + topTmpId).fadeTo(options.topBottomDanmuTime, $(element).data("opacity"), function () {
  466. me.topRows[$(this).data("row")]=0;
  467. $(this).remove();
  468.  
  469. }
  470. );
  471. }
  472. else if (danmaku.position == 2) {
  473. var bottomTmpId = me.id + "bottom" + parseInt(10000 * Math.random()).toString();
  474. $("#" + me.id + "tempDanmaku").attr("id", bottomTmpId);
  475. var temRow=me.getBottomRow(me);
  476. $(element).data("bottomSpace", options.FontSizeBig*temRow);
  477. me.bottomRows[temRow]=1;
  478. $("#" + bottomTmpId).css({
  479. "width": options.width
  480. , "left": "0"
  481. , "text-align": "center"
  482. , "position": "absolute"
  483. , "bottom": 0 + $(element).data("bottomSpace")
  484. });
  485. $("#" + bottomTmpId).data("row",temRow);
  486. $("#" + bottomTmpId).fadeTo(options.topBottomDanmuTime, $(element).data("opacity"), function () {
  487. me.bottomRows[$(this).data("row")]=0;
  488. $(this).remove();
  489. }
  490. );
  491.  
  492. } //else if
  493. danmus[i] = danmaku;
  494. } // for in danmus
  495. $(element).data("danmuList")[nowTime] = danmus
  496. } //if (danmus)
  497. me.launched.push($(element).data("nowTime"));
  498. // }, 0);
  499.  
  500. //循环
  501. if (index == options.sumTime && options.isLoop) {
  502. me.$timer.timer('stop');
  503. me.$timer.timer('start');
  504. }
  505.  
  506. })
  507. }
  508. });
  509. };
  510.  
  511.  
  512. Danmu.DEFAULTS = {
  513. left: 0,
  514. top: 0,
  515. height: 360,
  516. width: 640,
  517. zindex: 200,
  518. speed: 8000,
  519. sumTime: 65535,
  520. danmuLoop: false,
  521. danmuList: {},
  522. defaultFontColor: "#FFFFFF",
  523. fontSizeSmall: 16,
  524. FontSizeBig: 24,
  525. opacity: "0.9",
  526. topBottomDanmuTime: 6000,
  527. SubtitleProtection: false,
  528. positionOptimize: false,
  529. maxCountInScreen: 40,
  530. maxCountPerSec: 10
  531. };
  532.  
  533.  
  534. Danmu.prototype.danmuStart = function () {
  535. this.$timer.timer('start');
  536. this.$element.data("paused", 0);
  537. };
  538.  
  539.  
  540. Danmu.prototype.danmuStop = function () {
  541. this.$timer.timer('stop');
  542. $("#" + this.id + ' .danmaku').remove();
  543. nowTime = 0;
  544. this.$element.data("paused", 1);
  545. this.$element.data("nowTime", 0);
  546. };
  547.  
  548.  
  549. Danmu.prototype.danmuPause = function () {
  550. this.$timer.timer('pause');
  551. $("#" + this.id + ' .danmaku').pause();
  552. this.$element.data("paused", 1);
  553. };
  554.  
  555.  
  556. Danmu.prototype.danmuResume = function () {
  557. this.$timer.timer('resume');
  558. $("#" + this.id + ' .danmaku').resume();
  559. this.$element.data("paused", 0);
  560. };
  561.  
  562. Danmu.prototype.danmuHideAll = function () {
  563. $("#" + this.id + ' .danmaku').css({"opacity": 0});
  564. this.initRows(this);
  565. };
  566.  
  567.  
  568. Danmu.prototype.setTime = function (arg) {
  569. $("#" + this.id + ' .danmaku').remove();
  570. this.$element.data("nowTime", arg);
  571.  
  572. };
  573.  
  574. Danmu.prototype.setOpacity = function (arg) {
  575. $("#" + this.id + ' .danmaku').css("opacity", arg);
  576. this.$element.data("opacity", arg);
  577.  
  578. };
  579.  
  580.  
  581. Danmu.prototype.addDanmu = function (arg) {
  582. if (arg instanceof Array) {
  583. for (var i in arg) {
  584. if (this.$element.data("danmuList")[arg[i]["time"]]) {
  585. this.$element.data("danmuList")[arg[i]["time"]].push(arg[i]);
  586. }
  587. else {
  588. this.$element.data("danmuList")[arg[i]["time"]] = [];
  589. this.$element.data("danmuList")[arg[i]["time"]].push(arg[i]);
  590. }
  591. }
  592. }
  593. else {
  594. if (this.$element.data("danmuList")[arg.time]) {
  595. this.$element.data("danmuList")[arg.time].push(arg);
  596. }
  597. else {
  598. this.$element.data("danmuList")[arg.time] = [];
  599. this.$element.data("danmuList")[arg.time].push(arg);
  600. }
  601. }
  602. };
  603.  
  604.  
  605. function Plugin(option, arg) {
  606. return this.each(function () {
  607. var $this = $(this);
  608. var options = $.extend({}, Danmu.DEFAULTS, typeof option == 'object' && option);
  609. var data = $this.data('danmu');
  610. var action = typeof option == 'string' ? option : NaN;
  611. if (!data) $this.data('danmu', (data = new Danmu(this, options)));
  612. if (action) data[action](arg);
  613. })
  614. };
  615.  
  616.  
  617. $.fn.danmu = Plugin;
  618. $.fn.danmu.Constructor = Danmu;
  619.  
  620.  
  621. })(jQuery);