vue-danmaku
Version:
基于 Vue 的弹幕交互组件 | A danmaku component for Vue
2 lines (1 loc) • 10.1 kB
JavaScript
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("vue")):"function"==typeof define&&define.amd?define(["exports","vue"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).VueDanmaku={},e.Vue)}(this,(function(e,t){"use strict";const n=new Map,a=new Map;function o(e,t,o,s,r,l){const i=a.get(e);if(void 0===i)return;const u=performance.now(),d=i,c=-t,f=Math.abs(c-d)/s*1e3;const m=requestAnimationFrame((function t(s){if(r())return;const i=s-u;if(i>=f)return void l(e);const m=d+(c-d)*(i/f);e.style.transform=`translateX(${m-o}px)`,a.set(e,m);const p=requestAnimationFrame(t);n.set(e,p)}));n.set(e,m)}function s(e){const t=n.get(e);t&&(cancelAnimationFrame(t),n.delete(e))}function r(){n.forEach((e=>{cancelAnimationFrame(e)})),n.clear(),a.clear()}var l=t.defineComponent({name:"vue-danmaku",components:{},props:{danmus:{type:Array,required:!0,default:()=>[]},channels:{type:Number,default:0},autoplay:{type:Boolean,default:!0},loop:{type:Boolean,default:!1},randomChannel:{type:Boolean,default:!1},isSuspend:{type:Boolean,default:!1},performanceMode:{type:Boolean,default:!0},debounce:{type:Number,default:100},speeds:{type:Number,default:200},top:{type:Number,default:4},right:{type:Number,default:0},zIndex:{type:Number,default:10}},emits:["update:danmus","list-end","play-end","dm-over","dm-out","dm-click","dm-remove","error"],setup(e,{emit:l,slots:i,expose:u}){let d=t.ref(document.createElement("div")),c=t.ref(document.createElement("div"));const f=t.ref(0),m=t.ref(0);let p=0;const h=t.ref(0),v=t.ref(0),g=t.ref(0),y=t.ref(!1),x=t.ref(!1),T=t.ref({}),M=t.ref([...e.danmus]);t.watch((()=>e.danmus),(e=>{M.value=[...e]}),{deep:!0}),t.watch(M,(e=>{l("update:danmus",e)}),{deep:!0});const _=t.reactive({channels:t.computed((()=>e.channels||h.value)),autoplay:t.computed((()=>e.autoplay)),loop:t.computed((()=>e.loop)),debounce:t.computed((()=>e.debounce)),randomChannel:t.computed((()=>e.randomChannel))}),E=t.reactive({height:t.computed((()=>v.value)),speeds:t.computed((()=>e.speeds)),top:t.computed((()=>e.top)),right:t.computed((()=>e.right))});function A(){k(),e.isSuspend&&(c.value.addEventListener("mouseover",L),c.value.addEventListener("mouseout",$)),_.autoplay&&w()}function k(){if(f.value=d.value.offsetWidth,m.value=d.value.offsetHeight,0===f.value||0===m.value){const e="获取不到容器宽高";l("error",{message:e,code:"CONTAINER_SIZE_ERROR"}),console.error(`[vue-danmaku] ${e}`)}}function w(){x.value=!1,p||(p=window.setInterval((()=>function(){if(!x.value&&M.value.length)if(g.value>M.value.length-1){const e=c.value.children.length;_.loop&&(e<g.value&&(l("list-end"),g.value=0),C())}else C()}()),_.debounce)),e.performanceMode&&function(){if(c.value){Array.from(c.value.querySelectorAll(".dm")).forEach((e=>{o(e,e.offsetWidth,f.value,E.speeds,(()=>x.value),D)}))}}()}function C(o){const s=_.loop?g.value%M.value.length:g.value,r=function(e,n){const a=t.createApp({render:()=>t.h("div",{},[i.dm&&i.dm({danmu:e,index:n})])}),o=a.mount(document.createElement("div"));return o.$el.__vueApp=a,o}(o||M.value[s],s).$el;r.classList.add("dm"),c.value.appendChild(r),r.style.opacity="0",r._vueInstance={instance:r.__vueParentComponent||{ctx:{}},el:r},t.nextTick((()=>{E.height||(v.value=r.offsetHeight),_.channels||(h.value=Math.floor(m.value/(E.height+E.top))),function(t,o){let s=function(e){let t=[...Array(_.channels).keys()];_.randomChannel&&(t=t.sort((()=>.5-Math.random())));for(let n of t){const t=T.value[n];if(!t||!t.length)return T.value[n]=[e],n%_.channels;for(let a=0;a<t.length;a++){const o=N(t[a])-10;if(o<=.88*(e.offsetWidth-t[a].offsetWidth)||o<=0)break;if(a===t.length-1)return T.value[n].push(e),n%_.channels}}return-1}(t);if(s>=0){const r=t.offsetWidth,i=E.height;t.dataset.index=`${o}`,t.dataset.channel=s.toString(),t.style.opacity="1",t.style.top=s*(i+E.top)+"px",t.style.width=r+E.right+"px",t.style.zIndex=e.zIndex.toString();const u=e=>{l("dm-click",{el:t,index:o,danmu:M.value[o],event:e})};if(t.addEventListener("click",u),t._clickHandler=u,e.performanceMode)!function(e,t,o,s,r,l){e.style.transform="translateX(0px)",e.style.left=`${o}px`;const i=performance.now(),u=o/s*1e3,d=o,c=-t;a.set(e,d);const f=requestAnimationFrame((function t(o){if(r())return;const s=o-i;if(s>=u)return void l(e);const f=d+s/u*(c-d);e.style.transform=`translateX(${f-d}px)`,a.set(e,f);const m=requestAnimationFrame(t);n.set(e,m)}));n.set(e,f)}(t,r,f.value,E.speeds,(()=>x.value),D);else{t.classList.add("move"),t.style.setProperty("--dm-scroll-width",`-${f.value+r}px`),t.style.left=`${f.value}px`,t.style.animationDuration=f.value/E.speeds+"s";const e=()=>{Number(t.dataset.index)!==M.value.length-1||_.loop||l("play-end",t.dataset.index),W(t),c.value&&t.parentNode===c.value&&c.value.removeChild(t)};t._animationEndHandler=e,t.addEventListener("animationend",e)}g.value++}else W(t),c.value&&t.parentNode===c.value&&c.value.removeChild(t)}(r,s)}))}function N(e){const t=e.offsetWidth||parseInt(e.style.width),n=e.getBoundingClientRect().right||c.value.getBoundingClientRect().right+t;return c.value.getBoundingClientRect().right-n}function b(){clearInterval(p),p=0,g.value=0}function F(){if(c.value){Array.from(c.value.querySelectorAll(".dm")).forEach((e=>{W(e)})),c.value.innerHTML=""}e.performanceMode&&r(),T.value={},x.value=!0,y.value=!1,b()}function D(e){Number(e.dataset.index)!==M.value.length-1||_.loop||l("play-end",e.dataset.index);const t=Number(e.dataset.index);l("dm-remove",{el:e,index:t,danmu:t>=0?M.value[t]:null}),W(e),c.value&&e.parentNode===c.value&&c.value.removeChild(e)}t.onMounted((()=>{A()})),t.onBeforeUnmount((()=>{F(),b(),c.value&&(c.value.removeEventListener("mouseover",L),c.value.removeEventListener("mouseout",$)),e.performanceMode&&r()}));let I=[];function L(t){let n=t.target;n.className.includes("dm")||(n=n.closest(".dm")||n),n.className.includes("dm")&&(I.includes(n)||(l("dm-over",{el:n}),n.style.zIndex=(e.zIndex+1).toString(),e.performanceMode?s(n):n.classList.add("pause"),I.push(n)))}function $(e){let t=e.target;t.className.includes("dm")||(t=t.closest(".dm")||t),t.className.includes("dm")&&(l("dm-out",{el:t}),S(t),I.forEach(S),I=[])}function S(t){if(t.style.zIndex=e.zIndex.toString(),e.performanceMode){o(t,t.offsetWidth,f.value,E.speeds,(()=>x.value),D)}else t.classList.remove("pause")}function W(t){e.performanceMode?s(t):t._animationEndHandler&&(t.removeEventListener("animationend",t._animationEndHandler),delete t._animationEndHandler),t._clickHandler&&(t.removeEventListener("click",t._clickHandler),delete t._clickHandler);const n=t.dataset.channel?parseInt(t.dataset.channel):-1;if(n>=0&&T.value[n]){const e=T.value[n].indexOf(t);e>-1&&T.value[n].splice(e,1)}if(t.__vueApp){try{t.__vueApp.unmount()}catch(e){console.warn("卸载组件实例失败",e)}delete t.__vueApp}t._vueInstance&&delete t._vueInstance}return{container:d,dmContainer:c,hidden:y,paused:x,danmuList:M,getPlayState:function(){return!x.value},resize:function(){const t=f.value;k();const n=c.value.getElementsByClassName("dm");for(let a=0;a<n.length;a++){const r=n[a],l=r.offsetWidth;if(e.performanceMode){let e=(t-(r.getBoundingClientRect().left-d.value.getBoundingClientRect().left))/(t+l);if(e=Math.max(0,Math.min(1,e)),s(r),e>=1)D(r);else{const t=f.value-(f.value+l)*e;r.style.left=`${f.value}px`,r.style.transform=`translateX(${t-f.value}px)`,o(r,l,f.value,E.speeds,(()=>x.value),D)}}else r.style.setProperty("--dm-scroll-width",`-${f.value+l}px`),r.style.left=`${f.value}px`,r.style.animationDuration=f.value/E.speeds+"s"}},play:w,pause:function(){x.value=!0,e.performanceMode&&(n.forEach((e=>{cancelAnimationFrame(e)})),n.clear())},stop:F,show:function(){y.value=!1},hide:function(){y.value=!0},reset:function(){v.value=0,A()},addDanmu:function(e,t="current"){if("current"===t){if(g.value===M.value.length)return M.value.push(e),M.value.length-1;{const t=g.value%M.value.length;return M.value.splice(t,0,e),t+1}}return M.value.push(e),M.value.length-1},insert:C}}});const i={ref:"container",class:"vue-danmaku"};l.render=function(e,n,a,o,s,r){return t.openBlock(),t.createElementBlock("div",i,[t.createElementVNode("div",{ref:"dmContainer",class:t.normalizeClass(["danmus",{show:!e.hidden},{paused:e.paused}])},null,2),t.renderSlot(e.$slots,"default")],512)},l.__file="src/lib/Danmaku.vue";class u{constructor(e=30,t){this.fpsMonitorTimer=0,this.lastTime=0,this.frames=0,this.fps=0,this.running=!1,this.fpsThreshold=e,this.onFpsDrop=t||(()=>{})}start(){if(this.running)return;this.running=!0;const e=()=>{if(!this.running)return;const t=performance.now();this.frames++,t>this.lastTime+1e3&&(this.fps=Math.round(1e3*this.frames/(t-this.lastTime)),this.fps<this.fpsThreshold&&this.onFpsDrop({fps:this.fps,threshold:this.fpsThreshold}),this.frames=0,this.lastTime=t),requestAnimationFrame(e)};this.fpsMonitorTimer=window.setTimeout((()=>{this.lastTime=performance.now(),requestAnimationFrame(e)}),1e3)}stop(){this.running=!1,this.fpsMonitorTimer&&(clearTimeout(this.fpsMonitorTimer),this.fpsMonitorTimer=0)}getCurrentFps(){return this.fps}setFpsDropCallback(e){this.onFpsDrop=e}setFpsThreshold(e){this.fpsThreshold=e}}class d{constructor(e=100,t){this.warningThreshold=e,this.onPerformanceWarning=t||(()=>{})}checkPerformance(e){e>this.warningThreshold&&this.onPerformanceWarning({count:e,threshold:this.warningThreshold,message:`当前弹幕数量(${e})超过阈值(${this.warningThreshold}),可能影响性能`})}setWarningCallback(e){this.onPerformanceWarning=e}setWarningThreshold(e){this.warningThreshold=e}}function c(e,t){return new u(e,t)}function f(e,t){return new d(e,t)}const m={DEFAULT_FPS_THRESHOLD:30,DEFAULT_WARNING_DANMU_COUNT:100};e.Danmaku=l,e.PERFORMANCE_CONSTANTS=m,e.createDanmakuMonitor=function(e={}){const t=c(e.fpsThreshold,e.onFpsDrop),n=f(e.warningThreshold,e.onPerformanceWarning);return{fpsMonitor:t,performanceMonitor:n,startAll(){t.start()},stopAll(){t.stop()},checkDanmakuCount(e){n.checkPerformance(e)}}},e.createDanmakuPerformanceMonitor=f,e.createFpsMonitor=c,e.default=l,Object.defineProperty(e,"__esModule",{value:!0})}));