3D球体词云

效果以下:
html

此次作的是组件重构,以前这个组件我是在网上找了个插件,基于tagCanvas插件开发的,可是这个组件是15年左右,很久远了哇,太老了不太好用,知足不了目前新项目的需求。
前端

吴大哥分享了个网上看到的demovue

https://github.com/kongkong99/sample-reels/blob/master/src/views/3D-wordCloud/index.vuegit

这个demo是用vue写的,我须要改动下适用于咱们开发框架的组件。github

目前工做的开发组,组件项目是基于LitElement,其实它就是一套web components,要使用到shadowDomweb

这里我主要是记录一下,代码转换的过程和组件代码须要本身注意的几个地方。typescript

根据demo代码,首先是在render函数中,定义个容器,该容器的宽高是组件的宽高。在这个容器中须要根据数据量生成span标签,显示文本内容。数组

通常动态生成html结构,是封装成一个方法,方法调用用${}包起来。微信

<style> .container { position: relative; width: ${this.width}px; height: ${this.height}px; } .tag { display: block; position: absolute; left: 0px; top: 0px; font-size: ${this.fontSize * rx}px; }</style><div class="container">${this.createTags()}</div>

demo是用v-for循环数据生成span标签,组件的写法须要改动成以下这样:框架

public createTags() { const { color, contentEle } = this; return this.wordData.map((item: IWordData, index: number) => { const styleObj = contentEle[index] ? { ...contentEle[index].style } : {}; return html` <span class="tag" style=" color: ${color[index % color.length]}; opacity: ${styleObj.opacity}; z-index: ${styleObj.zIndex}; transform: ${styleObj.transform}; "          >${item.name}</span> `; });}

这里遇到过一个问题,contentEle这个数组,没有contentEle[index]是否存在的话会报undefined的错,拿不到style

demo代码能够知道这个style是存在每一个tag运行是的位置数据,也就是绝对定位的topleft值,以及translate的值,还有一些层级透明度的值。demovue...contentEle[index].style这个写法,组件里不适用。

这个扩展运算符是对对象的深拷贝,须要用{}括起来。组件的style的写法,只能将对象中值挨个拿出来写,不能像vue中直接写个对象。

demovue写法中created周期函数中初始化了datacontentEle数组,这个阶段作的事情等同于组件写法的isFirstUpdated中执行阶段,可是contentEle数组是基于数据生成的,可是isFirstUpdated中还拿不到数据,所以在isFirstUpdatedelse中并监听数据的变化时执行相同的代码。

public updateComponent( isFirstUpdated: boolean, properties: Map<string | number | symbol, unknown>) { if (!isFirstUpdated) { if (properties.has("wordData")) { this.contentEle = this.wordData.map(() => ({ x: 0, y: 0, z: 0, style: {}, })); this.initSpin();    }  }}

下面的代码和demo的一致。

其中执行动画的代码作了一些改变。须要实现的效果是,球体词云自动旋转,鼠标移入时中止,移除回复旋转。

isFirstUpdated中执行以下代码:

window.addEventListener("mousemove", (e: any) => { const container = this.shadowRoot!.querySelector(".container"); if (e.path.includes(container)) { this.pauseAnimate(); } else { this.restartAnimate(); }});

先获取该组件shadowDom中定义的container容器,在鼠标移动的时候,来判断鼠标事件e中的pathdom节点数上有没有当前这个组件的容器,有的话就说明是鼠标移入了就须要中止旋转,不然就是移出能够恢复旋转。

e.path会拿到当前鼠标所在的dom以及这个dom节点往上一直到window的全部节点。

暂停和恢复动画的方法中只是修改一个全局的布尔值类型的变量。

再根据这个变量值来判断是否调用demoanimate方法里调用的那几个方法。

public spinAnimate() { if (!this.animatePause) { this.rotateX(); this.rotateY(); this.move(); } this.animateId = requestAnimationFrame(() => { cancelAnimationFrame(this.animateId); this.spinAnimate(); });}

demoanimate方法名和组件引用的基类中的animate方法重名,因此改了个方法名。

代码中关于球体的构建用到了三角函数,我实在没看懂。可是代码中不少地方都是写的固定值来计算位置,可组件是须要拖动边框改变宽高大小的,测试发现组件宽高很大时候就出现bug了,最好的解决方案就是定义个初始状态下宽高的值,计算一下组件宽高和初始宽高的比例,再将代码中全部的固定值都乘上这个比例值。

const rx = this.width / this.originWidth;const ry = this.height / this.originHeight;

最终组件效果:

最后,默默感谢吴大哥,但愿本身尽早能达到大佬们的技术水平。

本文分享自微信公众号 - 前端一块儿学(gh_3ba18d51f982)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索