vue-danmaku
Version:
基于 Vue 的弹幕交互组件 | A danmaku component for Vue
2 lines (1 loc) • 10.9 kB
JavaScript
import{defineComponent as e,ref as n,computed as t,reactive 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 v,renderSlot as m}from"vue";const f=new Map,p=new Map;function h(e,n,t,a,l,o,u){const s=-1*(u||-1);e.style.transform="translateX(0px)",e.style[s<0?"right":"left"]=`${t}px`;const r=performance.now(),i=t/a*1e3,d=t*s,c=-n*s;p.set(e,d);const v=requestAnimationFrame((function n(t){if(l())return;const a=t-r;if(a>=i)return void o(e);const u=d+(c-d)*(a/i);e.style.transform=`translateX(${u-d}px)`,p.set(e,u);const s=requestAnimationFrame(n);f.set(e,s)}));f.set(e,v)}function y(e,n,t,a,l,o,u){const s=p.get(e);if(void 0===s)return void h(e,n,t,a,l,o);const r=-1*(u||-1),i=t*r,d=-n*r,c=t/a*1e3,v=c*((i-s)/(i-d)),m=performance.now()-v;const y=requestAnimationFrame((function n(t){if(l())return;const a=t-m;if(a>=c)return void o(e);const u=i+(d-i)*(a/c);e.style.transform=`translateX(${u-i}px)`,p.set(e,u);const s=requestAnimationFrame(n);f.set(e,s)}));f.set(e,y)}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},mirror:{type:Boolean,default:!1}},emits:["list-end","play-end","dm-over","dm-out","dm-click","dm-remove","error"],setup(e,{emit:i,slots:d,expose:c}){let v=n(document.createElement("div")),m=n(document.createElement("div"));const p=n(0),w=n(0),k=t((()=>e.mirror?1:-1)),E=t((()=>e.mirror?"right":"left"));let _=0;const C=n(0),b=n(0),L=n(0),A=n(!1),M=n(!0),N=n({}),B=n([...e.danmus]),I=n(new Set);let R=null;!function(e,n,a="modelValue",l){t({get:()=>e[a],set:e=>{n(`update:${a}`,l?l(e):e)}})}(e,i,"danmus");const z=a({channels:t((()=>e.channels||C.value)),debounce:t((()=>e.debounce)),randomChannel:t((()=>e.randomChannel))}),S=a({height:t((()=>b.value)),speeds:t((()=>e.speeds)),top:t((()=>e.top)),right:t((()=>e.right))});function O(){if(p.value=v.value.offsetWidth,w.value=v.value.offsetHeight,0===p.value||0===w.value){const e="获取不到容器宽高";i("error",{message:e,code:"CONTAINER_SIZE_ERROR"}),console.error(`[vue-danmaku] ${e}`)}}function $(){M.value&&(M.value=!1,_||(_=window.setInterval((()=>function(){if(!M.value&&B.value.length)if(L.value>B.value.length-1){const n=m.value.children.length;e.loop&&(n<L.value&&(i("list-end"),L.value=0,e.loopOnly&&I.value.clear()),H())}else H()}()),z.debounce)),e.performanceMode&&function(){if(m.value){Array.from(m.value.querySelectorAll(".dm")).forEach((e=>{y(e,e.offsetWidth,p.value,S.speeds,(()=>M.value),F,k.value)}))}}())}function H(n){const t=e.loop?L.value%B.value.length:L.value;if(e.loopOnly&&I.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||B.value[t],t).$el;a.classList.add("dm"),m.value.appendChild(a),a.style.opacity="0",a._vueInstance={instance:a.__vueParentComponent||{ctx:{}},el:a},u((()=>{S.height||(b.value=a.offsetHeight),z.channels||(C.value=Math.floor(w.value/(S.height+S.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=N.value[t];if(!n||!n.length)return N.value[t]=[e],t%z.channels;for(let a=0;a<n.length;a++){const l=W(n[a])-10;if(l<=.88*(e.offsetWidth-n[a].offsetWidth)||l<=0)break;if(a===n.length-1)return N.value[t].push(e),t%z.channels}}return-1}(n);if(a>=0){const l=n.offsetWidth,o=S.height;n.dataset.index=`${t}`,n.dataset.channel=a.toString(),n.style.opacity="1",n.style.top=a*(o+S.top)+"px",n.style.width=l+S.right+"px",n.style.zIndex=e.zIndex.toString();const u=e=>{i("dm-click",{el:n,index:t,danmu:B.value[t],event:e})};if(n.addEventListener("click",u),n._clickHandler=u,e.loop&&e.loopOnly&&I.value.add(t),e.performanceMode)h(n,l,p.value,S.speeds,(()=>M.value),F,k.value);else{n.classList.add("move"),n.style.setProperty("--dm-scroll-width",(p.value+l)*k.value+"px"),n.style[E.value]=`${p.value}px`,n.style.animationDuration=p.value/S.speeds+"s";const t=()=>{Number(n.dataset.index)!==B.value.length-1||e.loop||i("play-end",n.dataset.index);const t=Number(n.dataset.index);e.loop&&e.loopOnly&&t>=0&&I.value.delete(t),i("dm-remove",{el:n,index:t,danmu:t>=0?B.value[t]:null}),Z(n),m.value&&n.parentNode===m.value&&m.value.removeChild(n)};n._animationEndHandler=t,n.addEventListener("animationend",t)}L.value++}else Z(n),m.value&&n.parentNode===m.value&&m.value.removeChild(n)}(a,t)}))}function W(n){return e.mirror?function(e){const n=e.offsetWidth||parseInt(e.style.width),t=e.getBoundingClientRect().left||m.value.getBoundingClientRect().left+n;return m.value.getBoundingClientRect().left+t}(n):function(e){const n=e.offsetWidth||parseInt(e.style.width),t=e.getBoundingClientRect().right||m.value.getBoundingClientRect().right+n;return m.value.getBoundingClientRect().right-t}(n)}function X(){clearInterval(_),_=0,L.value=0,I.value.clear()}function q(){if(X(),m.value){Array.from(m.value.querySelectorAll(".dm")).forEach((e=>{Z(e)})),m.value.innerHTML=""}e.performanceMode&&x(),N.value={},M.value=!0}function F(n){Number(n.dataset.index)!==B.value.length-1||e.loop||i("play-end",n.dataset.index);const t=Number(n.dataset.index);e.loop&&e.loopOnly&&t>=0&&I.value.delete(t),i("dm-remove",{el:n,index:L,danmu:t>=0?B.value[t]:null}),Z(n),m.value&&n.parentNode===m.value&&m.value.removeChild(n)}function D(){const n=p.value;O();const t=m.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-v.value.getBoundingClientRect().left))/(n+o);if(e=Math.max(0,Math.min(1,e)),g(l),e>=1)F(l);else{const n=p.value-(p.value+o)*e;l.style[E.value]=`${p.value}px`,l.style.transform=`translateX(${n-p.value}px)`,y(l,o,p.value,S.speeds,(()=>M.value),F,k.value)}}else l.style.setProperty("--dm-scroll-width",(p.value+o)*k.value+"px"),l.style[E.value]=`${p.value}px`,l.style.animationDuration=p.value/S.speeds+"s"}}l((()=>{!function(){O(),e.isSuspend&&(m.value.addEventListener("mouseover",P),m.value.addEventListener("mouseout",U)),d.danmu||(i("error",{message:'没有提供弹幕插槽内容(slot="danmu"),无法展示弹幕',code:"NO_DANMU_SLOT"}),console.error('[vue-danmaku] 警告:没有提供弹幕插槽内容(slot="danmu"),无法展示弹幕'));e.autoplay&&$()}(),e.autoResize&&function(){if("undefined"!=typeof ResizeObserver)R=new ResizeObserver((()=>{D()})),R.observe(v.value);else{const e=()=>D();window.addEventListener("resize",e),o((()=>{window.removeEventListener("resize",e)}))}}()})),o((()=>{q(),m.value&&(m.value.removeEventListener("mouseover",P),m.value.removeEventListener("mouseout",U)),R&&(R.disconnect(),R=null),e.performanceMode&&x()}));let T=[];function P(n){let t=n.target;t.classList.contains("dm")||(t=t.closest(".dm")||t),t.classList.contains("dm")&&(T.includes(t)||(i("dm-over",{el:t}),t.style.zIndex=(e.zIndex+1).toString(),e.performanceMode?g(t):t.classList.add("pause"),T.push(t)))}function U(e){let n=e.target;n.classList.contains("dm")||(n=n.closest(".dm")||n),n.classList.contains("dm")&&(i("dm-out",{el:n}),V(n),T.forEach(V),T=[])}function V(n){if(n.style.zIndex=e.zIndex.toString(),e.performanceMode){y(n,n.offsetWidth,p.value,S.speeds,(()=>M.value),F,k.value)}else n.classList.remove("pause")}function Z(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&&N.value[t]){const e=N.value[t].indexOf(n);e>-1&&N.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:v,dmContainer:m,hidden:A,paused:M,danmuList:B,getPlayState:function(){return!M.value},getMaxChannels:function(){return S.height?Math.floor(w.value/(S.height+S.top)):0},resize:D,play:$,pause:function(){M.value=!0,e.performanceMode&&(f.forEach((e=>{cancelAnimationFrame(e)})),f.clear())},stop:q,show:function(){A.value=!1},hide:function(){A.value=!0},clear:X,reset:function(){q(),b.value=0,A.value=!1,O()},addDanmu:function(e,n="current"){if("current"===n){if(L.value===B.value.length)return B.value.push(e),B.value.length-1;{const n=L.value%B.value.length;return B.value.splice(n,0,e),n+1}}return B.value.push(e),B.value.length-1},insert:H}}});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:v(["danmus",{show:!e.hidden},{paused:e.paused}])},null,2),m(e.$slots,"default")],512)},w.__file="src/lib/Danmaku.vue";export{w as Danmaku,w as default};