UNPKG

vue-danmaku

Version:

基于 Vue 的弹幕交互组件 | A danmaku component for Vue

2 lines (1 loc) 11 kB
!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,l,r,s){e.style.transform="translateX(0px)",e.style.left=`${o}px`;const u=performance.now(),i=o/l*1e3,d=o,c=-t;a.set(e,d);const f=requestAnimationFrame((function t(o){if(r())return;const l=o-u;if(l>=i)return void s(e);const f=d+(c-d)*(l/i);e.style.transform=`translateX(${f-d}px)`,a.set(e,f);const m=requestAnimationFrame(t);n.set(e,m)}));n.set(e,f)}function l(e,t,l,r,s,u){const i=a.get(e);if(void 0===i)return void o(e,t,l,r,s,u);const d=l,c=-t,f=l/r*1e3,m=f*((d-i)/(d-c)),p=performance.now()-m;const v=requestAnimationFrame((function t(o){if(s())return;const l=o-p;if(l>=f)return void u(e);const r=d+(c-d)*(l/f);e.style.transform=`translateX(${r-d}px)`,a.set(e,r);const i=requestAnimationFrame(t);n.set(e,i)}));n.set(e,v)}function r(e){const t=n.get(e);t&&(cancelAnimationFrame(t),n.delete(e))}function s(){n.forEach((e=>{cancelAnimationFrame(e)})),n.clear(),a.clear()}var u=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},loopOnly:{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:1},autoResize:{type:Boolean,default:!0}},emits:["list-end","play-end","dm-over","dm-out","dm-click","dm-remove","error"],setup(e,{emit:a,slots:u,expose:i}){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 v=t.ref(0),h=t.ref(0),g=t.ref(0),y=t.ref(!1),x=t.ref(!0),T=t.ref({}),M=t.ref([...e.danmus]),_=t.ref(new Set);let E=null;!function(e,n,a="modelValue",o){t.computed({get:()=>e[a],set:e=>{n(`update:${a}`,o?o(e):e)}})}(e,a,"danmus");const w=t.reactive({channels:t.computed((()=>e.channels||v.value)),debounce:t.computed((()=>e.debounce)),randomChannel:t.computed((()=>e.randomChannel))}),A=t.reactive({height:t.computed((()=>h.value)),speeds:t.computed((()=>e.speeds)),top:t.computed((()=>e.top)),right:t.computed((()=>e.right))});function N(){if(f.value=d.value.offsetWidth,m.value=d.value.offsetHeight,0===f.value||0===m.value){const e="获取不到容器宽高";a("error",{message:e,code:"CONTAINER_SIZE_ERROR"}),console.error(`[vue-danmaku] ${e}`)}}function k(){x.value&&(x.value=!1,p||(p=window.setInterval((()=>function(){if(!x.value&&M.value.length)if(g.value>M.value.length-1){const t=c.value.children.length;e.loop&&(t<g.value&&(a("list-end"),g.value=0,e.loopOnly&&_.value.clear()),C())}else C()}()),w.debounce)),e.performanceMode&&function(){if(c.value){Array.from(c.value.querySelectorAll(".dm")).forEach((e=>{l(e,e.offsetWidth,f.value,A.speeds,(()=>x.value),D)}))}}())}function C(n){const l=e.loop?g.value%M.value.length:g.value;if(e.loopOnly&&_.value.has(l))return;const r=function(e,n){const a=t.createApp({render:()=>t.h("div",{},[u.danmu&&u.danmu({danmu:e,index:n})])}),o=a.mount(document.createElement("div"));return o.$el.__vueApp=a,o}(n||M.value[l],l).$el;r.classList.add("dm"),c.value.appendChild(r),r.style.opacity="0",r._vueInstance={instance:r.__vueParentComponent||{ctx:{}},el:r},t.nextTick((()=>{A.height||(h.value=r.offsetHeight),w.channels||(v.value=Math.floor(m.value/(A.height+A.top))),function(t,n){let l=function(e){let t=[...Array(w.channels).keys()];w.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%w.channels;for(let a=0;a<t.length;a++){const o=b(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%w.channels}}return-1}(t);if(l>=0){const r=t.offsetWidth,s=A.height;t.dataset.index=`${n}`,t.dataset.channel=l.toString(),t.style.opacity="1",t.style.top=l*(s+A.top)+"px",t.style.width=r+A.right+"px",t.style.zIndex=e.zIndex.toString();const u=e=>{a("dm-click",{el:t,index:n,danmu:M.value[n],event:e})};if(t.addEventListener("click",u),t._clickHandler=u,e.loop&&e.loopOnly&&_.value.add(n),e.performanceMode)o(t,r,f.value,A.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/A.speeds+"s";const n=()=>{Number(t.dataset.index)!==M.value.length-1||e.loop||a("play-end",t.dataset.index);const n=Number(t.dataset.index);e.loop&&e.loopOnly&&n>=0&&_.value.delete(n),a("dm-remove",{el:t,index:n,danmu:n>=0?M.value[n]:null}),$(t),c.value&&t.parentNode===c.value&&c.value.removeChild(t)};t._animationEndHandler=n,t.addEventListener("animationend",n)}g.value++}else $(t),c.value&&t.parentNode===c.value&&c.value.removeChild(t)}(r,l)}))}function b(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 F(){clearInterval(p),p=0,g.value=0,_.value.clear()}function L(){if(F(),c.value){Array.from(c.value.querySelectorAll(".dm")).forEach((e=>{$(e)})),c.value.innerHTML=""}e.performanceMode&&s(),T.value={},x.value=!0}function D(t){Number(t.dataset.index)!==M.value.length-1||e.loop||a("play-end",t.dataset.index);const n=Number(t.dataset.index);e.loop&&e.loopOnly&&n>=0&&_.value.delete(n),a("dm-remove",{el:t,index:g,danmu:n>=0?M.value[n]:null}),$(t),c.value&&t.parentNode===c.value&&c.value.removeChild(t)}function O(){const t=f.value;N();const n=c.value.getElementsByClassName("dm");for(let a=0;a<n.length;a++){const o=n[a],s=o.offsetWidth;if(e.performanceMode){let e=(t-(o.getBoundingClientRect().left-d.value.getBoundingClientRect().left))/(t+s);if(e=Math.max(0,Math.min(1,e)),r(o),e>=1)D(o);else{const t=f.value-(f.value+s)*e;o.style.left=`${f.value}px`,o.style.transform=`translateX(${t-f.value}px)`,l(o,s,f.value,A.speeds,(()=>x.value),D)}}else o.style.setProperty("--dm-scroll-width",`-${f.value+s}px`),o.style.left=`${f.value}px`,o.style.animationDuration=f.value/A.speeds+"s"}}t.onMounted((()=>{!function(){N(),e.isSuspend&&(c.value.addEventListener("mouseover",I),c.value.addEventListener("mouseout",R)),u.danmu||(a("error",{message:'没有提供弹幕插槽内容(slot="danmu"),无法展示弹幕',code:"NO_DANMU_SLOT"}),console.error('[vue-danmaku] 警告:没有提供弹幕插槽内容(slot="danmu"),无法展示弹幕'));e.autoplay&&k()}(),e.autoResize&&function(){if("undefined"!=typeof ResizeObserver)E=new ResizeObserver((()=>{O()})),E.observe(d.value);else{const e=()=>O();window.addEventListener("resize",e),t.onBeforeUnmount((()=>{window.removeEventListener("resize",e)}))}}()})),t.onBeforeUnmount((()=>{L(),c.value&&(c.value.removeEventListener("mouseover",I),c.value.removeEventListener("mouseout",R)),E&&(E.disconnect(),E=null),e.performanceMode&&s()}));let B=[];function I(t){let n=t.target;n.className.includes("dm")||(n=n.closest(".dm")||n),n.className.includes("dm")&&(B.includes(n)||(a("dm-over",{el:n}),n.style.zIndex=(e.zIndex+1).toString(),e.performanceMode?r(n):n.classList.add("pause"),B.push(n)))}function R(e){let t=e.target;t.className.includes("dm")||(t=t.closest(".dm")||t),t.className.includes("dm")&&(a("dm-out",{el:t}),S(t),B.forEach(S),B=[])}function S(t){if(t.style.zIndex=e.zIndex.toString(),e.performanceMode){l(t,t.offsetWidth,f.value,A.speeds,(()=>x.value),D)}else t.classList.remove("pause")}function $(t){e.performanceMode?r(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},getMaxChannels:function(){return A.height?Math.floor(m.value/(A.height+A.top)):0},resize:O,play:k,pause:function(){x.value=!0,e.performanceMode&&(n.forEach((e=>{cancelAnimationFrame(e)})),n.clear())},stop:L,show:function(){y.value=!1},hide:function(){y.value=!0},clear:F,reset:function(){L(),h.value=0,y.value=!1,N()},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"};u.render=function(e,n,a,o,l,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)},u.__file="src/lib/Danmaku.vue";class d{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 c{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 f(e,t){return new d(e,t)}function m(e,t){return new c(e,t)}const p={DEFAULT_FPS_THRESHOLD:30,DEFAULT_WARNING_DANMU_COUNT:100};e.Danmaku=u,e.PERFORMANCE_CONSTANTS=p,e.createDanmakuMonitor=function(e={}){const t=f(e.fpsThreshold,e.onFpsDrop),n=m(e.warningThreshold,e.onPerformanceWarning);return{fpsMonitor:t,performanceMonitor:n,startAll(){t.start()},stopAll(){t.stop()},checkDanmakuCount(e){n.checkPerformance(e)}}},e.createDanmakuPerformanceMonitor=m,e.createFpsMonitor=f,e.default=u,Object.defineProperty(e,"__esModule",{value:!0})}));