vue-danmaku
Version:
基于 Vue 的弹幕交互组件 | A danmaku component for Vue
2 lines (1 loc) • 10.4 kB
JavaScript
import{defineComponent as e,ref as n,reactive as t,computed as a,onMounted as l,onBeforeUnmount as o,nextTick as u,createApp as s,h as r,openBlock as i,createElementBlock as d,createElementVNode as c,normalizeClass as m,renderSlot as v}from"vue";const f=new Map,p=new Map;function h(e,n,t,a,l,o){e.style.transform="translateX(0px)",e.style.left=`${t}px`;const u=performance.now(),s=t/a*1e3,r=t,i=-n;p.set(e,r);const d=requestAnimationFrame((function n(t){if(l())return;const a=t-u;if(a>=s)return void o(e);const d=r+(i-r)*(a/s);e.style.transform=`translateX(${d-r}px)`,p.set(e,d);const c=requestAnimationFrame(n);f.set(e,c)}));f.set(e,d)}function y(e,n,t,a,l,o){const u=p.get(e);if(void 0===u)return void h(e,n,t,a,l,o);const s=t,r=-n,i=t/a*1e3,d=i*((s-u)/(s-r)),c=performance.now()-d;const m=requestAnimationFrame((function n(t){if(l())return;const a=t-c;if(a>=i)return void o(e);const u=s+(r-s)*(a/i);e.style.transform=`translateX(${u-s}px)`,p.set(e,u);const d=requestAnimationFrame(n);f.set(e,d)}));f.set(e,m)}function g(e){const n=f.get(e);n&&(cancelAnimationFrame(n),f.delete(e))}function x(){f.forEach((e=>{cancelAnimationFrame(e)})),f.clear(),p.clear()}var w=e({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:i,slots:d,expose:c}){let m=n(document.createElement("div")),v=n(document.createElement("div"));const p=n(0),w=n(0);let k=0;const E=n(0),_=n(0),b=n(0),N=n(!1),C=n(!0),A=n({}),L=n([...e.danmus]),M=n(new Set);let I=null;!function(e,n,t="modelValue",l){a({get:()=>e[t],set:e=>{n(`update:${t}`,l?l(e):e)}})}(e,i,"danmus");const z=t({channels:a((()=>e.channels||E.value)),debounce:a((()=>e.debounce)),randomChannel:a((()=>e.randomChannel))}),B=t({height:a((()=>_.value)),speeds:a((()=>e.speeds)),top:a((()=>e.top)),right:a((()=>e.right))});function $(){if(p.value=m.value.offsetWidth,w.value=m.value.offsetHeight,0===p.value||0===w.value){const e="获取不到容器宽高";i("error",{message:e,code:"CONTAINER_SIZE_ERROR"}),console.error(`[vue-danmaku] ${e}`)}}function S(){C.value&&(C.value=!1,k||(k=window.setInterval((()=>function(){if(!C.value&&L.value.length)if(b.value>L.value.length-1){const n=v.value.children.length;e.loop&&(n<b.value&&(i("list-end"),b.value=0,e.loopOnly&&M.value.clear()),O())}else O()}()),z.debounce)),e.performanceMode&&function(){if(v.value){Array.from(v.value.querySelectorAll(".dm")).forEach((e=>{y(e,e.offsetWidth,p.value,B.speeds,(()=>C.value),W)}))}}())}function O(n){const t=e.loop?b.value%L.value.length:b.value;if(e.loopOnly&&M.value.has(t))return;const a=function(e,n){const t=s({render:()=>r("div",{},[d.danmu&&d.danmu({danmu:e,index:n})])}),a=t.mount(document.createElement("div"));return a.$el.__vueApp=t,a}(n||L.value[t],t).$el;a.classList.add("dm"),v.value.appendChild(a),a.style.opacity="0",a._vueInstance={instance:a.__vueParentComponent||{ctx:{}},el:a},u((()=>{B.height||(_.value=a.offsetHeight),z.channels||(E.value=Math.floor(w.value/(B.height+B.top))),function(n,t){let a=function(e){let n=[...Array(z.channels).keys()];z.randomChannel&&(n=n.sort((()=>.5-Math.random())));for(let t of n){const n=A.value[t];if(!n||!n.length)return A.value[t]=[e],t%z.channels;for(let a=0;a<n.length;a++){const l=R(n[a])-10;if(l<=.88*(e.offsetWidth-n[a].offsetWidth)||l<=0)break;if(a===n.length-1)return A.value[t].push(e),t%z.channels}}return-1}(n);if(a>=0){const l=n.offsetWidth,o=B.height;n.dataset.index=`${t}`,n.dataset.channel=a.toString(),n.style.opacity="1",n.style.top=a*(o+B.top)+"px",n.style.width=l+B.right+"px",n.style.zIndex=e.zIndex.toString();const u=e=>{i("dm-click",{el:n,index:t,danmu:L.value[t],event:e})};if(n.addEventListener("click",u),n._clickHandler=u,e.loop&&e.loopOnly&&M.value.add(t),e.performanceMode)h(n,l,p.value,B.speeds,(()=>C.value),W);else{n.classList.add("move"),n.style.setProperty("--dm-scroll-width",`-${p.value+l}px`),n.style.left=`${p.value}px`,n.style.animationDuration=p.value/B.speeds+"s";const t=()=>{Number(n.dataset.index)!==L.value.length-1||e.loop||i("play-end",n.dataset.index);const t=Number(n.dataset.index);e.loop&&e.loopOnly&&t>=0&&M.value.delete(t),i("dm-remove",{el:n,index:t,danmu:t>=0?L.value[t]:null}),U(n),v.value&&n.parentNode===v.value&&v.value.removeChild(n)};n._animationEndHandler=t,n.addEventListener("animationend",t)}b.value++}else U(n),v.value&&n.parentNode===v.value&&v.value.removeChild(n)}(a,t)}))}function R(e){const n=e.offsetWidth||parseInt(e.style.width),t=e.getBoundingClientRect().right||v.value.getBoundingClientRect().right+n;return v.value.getBoundingClientRect().right-t}function H(){clearInterval(k),k=0,b.value=0,M.value.clear()}function X(){if(H(),v.value){Array.from(v.value.querySelectorAll(".dm")).forEach((e=>{U(e)})),v.value.innerHTML=""}e.performanceMode&&x(),A.value={},C.value=!0}function W(n){Number(n.dataset.index)!==L.value.length-1||e.loop||i("play-end",n.dataset.index);const t=Number(n.dataset.index);e.loop&&e.loopOnly&&t>=0&&M.value.delete(t),i("dm-remove",{el:n,index:b,danmu:t>=0?L.value[t]:null}),U(n),v.value&&n.parentNode===v.value&&v.value.removeChild(n)}function q(){const n=p.value;$();const t=v.value.getElementsByClassName("dm");for(let a=0;a<t.length;a++){const l=t[a],o=l.offsetWidth;if(e.performanceMode){let e=(n-(l.getBoundingClientRect().left-m.value.getBoundingClientRect().left))/(n+o);if(e=Math.max(0,Math.min(1,e)),g(l),e>=1)W(l);else{const n=p.value-(p.value+o)*e;l.style.left=`${p.value}px`,l.style.transform=`translateX(${n-p.value}px)`,y(l,o,p.value,B.speeds,(()=>C.value),W)}}else l.style.setProperty("--dm-scroll-width",`-${p.value+o}px`),l.style.left=`${p.value}px`,l.style.animationDuration=p.value/B.speeds+"s"}}l((()=>{!function(){$(),e.isSuspend&&(v.value.addEventListener("mouseover",D),v.value.addEventListener("mouseout",T)),d.danmu||(i("error",{message:'没有提供弹幕插槽内容(slot="danmu"),无法展示弹幕',code:"NO_DANMU_SLOT"}),console.error('[vue-danmaku] 警告:没有提供弹幕插槽内容(slot="danmu"),无法展示弹幕'));e.autoplay&&S()}(),e.autoResize&&function(){if("undefined"!=typeof ResizeObserver)I=new ResizeObserver((()=>{q()})),I.observe(m.value);else{const e=()=>q();window.addEventListener("resize",e),o((()=>{window.removeEventListener("resize",e)}))}}()})),o((()=>{X(),v.value&&(v.value.removeEventListener("mouseover",D),v.value.removeEventListener("mouseout",T)),I&&(I.disconnect(),I=null),e.performanceMode&&x()}));let F=[];function D(n){let t=n.target;t.className.includes("dm")||(t=t.closest(".dm")||t),t.className.includes("dm")&&(F.includes(t)||(i("dm-over",{el:t}),t.style.zIndex=(e.zIndex+1).toString(),e.performanceMode?g(t):t.classList.add("pause"),F.push(t)))}function T(e){let n=e.target;n.className.includes("dm")||(n=n.closest(".dm")||n),n.className.includes("dm")&&(i("dm-out",{el:n}),P(n),F.forEach(P),F=[])}function P(n){if(n.style.zIndex=e.zIndex.toString(),e.performanceMode){y(n,n.offsetWidth,p.value,B.speeds,(()=>C.value),W)}else n.classList.remove("pause")}function U(n){e.performanceMode?g(n):n._animationEndHandler&&(n.removeEventListener("animationend",n._animationEndHandler),delete n._animationEndHandler),n._clickHandler&&(n.removeEventListener("click",n._clickHandler),delete n._clickHandler);const t=n.dataset.channel?parseInt(n.dataset.channel):-1;if(t>=0&&A.value[t]){const e=A.value[t].indexOf(n);e>-1&&A.value[t].splice(e,1)}if(n.__vueApp){try{n.__vueApp.unmount()}catch(e){console.warn("卸载组件实例失败",e)}delete n.__vueApp}n._vueInstance&&delete n._vueInstance}return{container:m,dmContainer:v,hidden:N,paused:C,danmuList:L,getPlayState:function(){return!C.value},getMaxChannels:function(){return B.height?Math.floor(w.value/(B.height+B.top)):0},resize:q,play:S,pause:function(){C.value=!0,e.performanceMode&&(f.forEach((e=>{cancelAnimationFrame(e)})),f.clear())},stop:X,show:function(){N.value=!1},hide:function(){N.value=!0},clear:H,reset:function(){X(),_.value=0,N.value=!1,$()},addDanmu:function(e,n="current"){if("current"===n){if(b.value===L.value.length)return L.value.push(e),L.value.length-1;{const n=b.value%L.value.length;return L.value.splice(n,0,e),n+1}}return L.value.push(e),L.value.length-1},insert:O}}});const k={ref:"container",class:"vue-danmaku"};!function(e,n){void 0===n&&(n={});var t=n.insertAt;if(e&&"undefined"!=typeof document){var a=document.head||document.getElementsByTagName("head")[0],l=document.createElement("style");l.type="text/css","top"===t&&a.firstChild?a.insertBefore(l,a.firstChild):a.appendChild(l),l.styleSheet?l.styleSheet.cssText=e:l.appendChild(document.createTextNode(e))}}(".vue-danmaku {\n position: relative;\n overflow: hidden;\n height: 100%;\n width: 100%;\n}\n.vue-danmaku .danmus {\n position: absolute;\n left: 0;\n top: 0;\n width: 100%;\n height: 100%;\n opacity: 0;\n -webkit-transition: all 0.3s;\n transition: all 0.3s;\n}\n.vue-danmaku .danmus.show {\n opacity: 1;\n}\n.vue-danmaku .danmus.paused .dm.move {\n animation-play-state: paused;\n}\n.vue-danmaku .danmus .dm {\n position: absolute;\n font-size: 20px;\n color: #666;\n white-space: pre;\n cursor: default;\n transform: translateX(0);\n transform-style: preserve-3d;\n}\n.vue-danmaku .danmus .dm.move {\n will-change: transform;\n animation-name: moveLeft;\n animation-timing-function: linear;\n animation-play-state: running;\n}\n.vue-danmaku .danmus .dm.pause {\n animation-play-state: paused;\n}\n@keyframes moveLeft {\n from {\n transform: translateX(0);\n }\n to {\n transform: translateX(var(--dm-scroll-width));\n }\n}\n@-webkit-keyframes moveLeft {\n from {\n -webkit-transform: translateX(0);\n }\n to {\n -webkit-transform: translateX(var(--dm-scroll-width));\n }\n}"),w.render=function(e,n,t,a,l,o){return i(),d("div",k,[c("div",{ref:"dmContainer",class:m(["danmus",{show:!e.hidden},{paused:e.paused}])},null,2),v(e.$slots,"default")],512)},w.__file="src/lib/Danmaku.vue";export{w as Danmaku,w as default};