上一篇文章我們介紹了數(shù)字孿生巴粪,和該技術下孿生風機的諸多優(yōu)點通今。本篇文章向大家介紹下孿生風機用了哪些前端技術?前端效果是如何實現(xiàn)的肛根?
一辫塌、Canvas
簡介
在web中,實現(xiàn)2D基本圖形及動畫效果派哲,首先會想到使用canvas臼氨。例如上圖,實現(xiàn)一個可以實時顯示風向角變化的效果圖芭届。canvas非常靈活储矩,能夠很好地融合JavaScript代碼并在瀏覽器內(nèi)繪制華麗的圖形感耙,擁有多種繪制路徑、形狀持隧、字符以及添加圖像等方法即硼。
面對各種復雜的圖形及效果,我們可以采用canvas框架屡拨,例如Konva只酥,它可以輕松的實現(xiàn)桌面應用和移動應用中的圖形交互交互效果,可以高效的實現(xiàn)動畫呀狼,變換裂允, 節(jié)點嵌套, 局部操作哥艇,濾鏡绝编,緩存,事件等功能貌踏,不僅僅適用于桌面與移動開發(fā)十饥, 還有更為廣泛的應用。
實現(xiàn)
在實現(xiàn)動畫實時變化效果時哩俭,如果每次風向發(fā)生改變绷跑,都需要重繪圖形,效果顯示難免有些單調(diào)凡资。重新繪圖的變化過程稱為突變動畫砸捏。
我們需要的是平滑的過渡效果,例如隙赁,風向角在上一時刻是36.89度垦藏,下一時刻是76.84度。動畫效果由36.89度漸變到76.84度的平滑效果伞访。
為避免突變動畫的情況掂骏,我們需要在componentWillUpdate中監(jiān)聽角度參數(shù)rotation的變化,當有新的角度參數(shù)傳入的時候厚掷,需要重新繪制圖形并將新的參數(shù)傳入弟灼。
代碼如下:
componentWillUpdate(nextProps) {
if (nextProps.rotation !== this.props.rotation) {
this.layer.destroy()
this.tween.destroy()
this.drawWind(nextProps)
}
}
//旋轉(zhuǎn)部分初始角度賦值為當前rotation。
let lineGroup = new Konva.Group({
x:radius,
y:radius,
rotation: this.props.rotation,//未變化時采用this.props值冒黑。動畫處用nextProps
})
//動畫部分旋轉(zhuǎn)角度賦值為新的rotation田绑。
this.tween = new Konva.Tween({
node: lineGroup,
easing: Konva.Easings.Linear,
duration: 0.5,
rotation: nextProps.rotation ? nextProps.rotation : nextProps.rotation === 0 ? 0 : this.props.rotation,
onFinish: () => {
this.tween.destroy()
}
});
二、Three.js
簡介
Three.js是基于原生WebGL封裝運行的三維引擎抡爹,在所有WebGL引擎中掩驱,Three.js是國內(nèi)文檔資料最多、使用最廣泛的三維引擎;Three.js是純渲染引擎欧穴,而且代碼易讀民逼,適合作為學習WebGL、3D圖形涮帘、3D數(shù)學應用的平臺拼苍,也可以做中小型的重表現(xiàn)的Web項目。
實現(xiàn)
風機模型加載完畢后调缨,當鼠標移動到風機各個零部件時映屋,所選部件增加線框以表示為選中狀態(tài)。如果在此之前有選中其它部件同蜻,需移除其它部件的選中狀態(tài)。當鼠標移出該部件的時候早处,將其線框移除湾蔓。
代碼如下:監(jiān)聽鼠標事件的onMouseMove。
// 鼠標移入事件
this.mouseMove = (event) => {
//點擊射線
let raycaster = new THREE.Raycaster();
// ?????? 注意此處的mouse必須設置砌梆,這樣下面才能判斷當前選中模型Group的單個組員
let mouse = new THREE.Vector2();
//將鼠標點擊位置的屏幕坐標轉(zhuǎn)成threejs中的標準坐標,具體解釋見代碼釋義
mouse.x = (event.offsetX / width) * 2 - 1;
mouse.y = -(event.offsetY / height) * 2 + 1;
//新建一個三維單位向量 假設z方向就是1
//根據(jù)照相機默责,把這個向量轉(zhuǎn)換到視點坐標系
let vector = new THREE.Vector3(mouse.x, mouse.y, 1).unproject(camera)
//通過鼠標點擊的位置(二維坐標)和當前相機的矩陣計算出射線位置
raycaster.setFromCamera(mouse, camera);
//在視點坐標系中形成射線,射線的起點向量是照相機, 射線的方向向量是照相機到點擊的點咸包,這個向量應該歸一標準化桃序。
raycaster.set(camera.position, vector.sub(camera.position).normalize())
//射線和模型求交,選中一系列直線
// 存放旋轉(zhuǎn)部分點擊
let intersectsFlabellum = raycaster.intersectObjects(flabellum.children, true)
// 存放旋轉(zhuǎn)部分之外的點擊
let intersectsObject = raycaster.intersectObjects(this.object.children, true)
if (intersectsFlabellum.length && !intersectsObject.length) {
if (intersectsFlabellum[0].object.name === '扇葉' && intersectsFlabellum.length < 3) {
if (this.state.mouseOver !== '扇葉') {
this.setState({ mouseOver: '扇葉' })
this.scene.remove(hubLineGroup)
}
} else if (intersectsFlabellum[0].object.name === '輪轂') {
if (this.state.mouseOver !== '輪轂') {
this.setState({ mouseOver: '輪轂' })
this.scene.add(hubLineGroup)
}
} else {
this.scene.remove(hubLineGroup)
if (!this.state.mouseSelect) {
this.setState({ mouseOver: '', control: true })
} else {
this.setState({ mouseOver: '' })
}
}
} else {
this.scene.remove(hubLineGroup)
}
if (intersectsObject.length) {
this.scene.remove(pitchGroup)
this.scene.remove(hubLineGroup)
if (intersectsObject[0].object.name === '主軸') {
if (this.state.mouseOver !== '主軸') {
this.setState({ mouseOver: '主軸' })
this.scene.add(mainAxleGroup)
this.scene.remove(gearGroup)
this.scene.remove(powerGroup)
}
} else if (intersectsObject[0].object.name === '齒輪結構') {
if (this.state.mouseOver !== '齒輪結構') {
this.setState({ mouseOver: '齒輪結構' })
this.scene.add(gearGroup)
this.scene.remove(mainAxleGroup)
this.scene.remove(powerGroup)
}
} else if (intersectsObject[0].object.name === '發(fā)電機箱') {
if (this.state.mouseOver !== '發(fā)電機箱') {
this.setState({ mouseOver: '發(fā)電機箱' })
this.scene.add(powerGroup)
this.scene.remove(mainAxleGroup)
this.scene.remove(gearGroup)
}
} else {
if (!this.state.mouseSelect) {
this.setState({ mouseOver: '', control: true })
} else {
this.setState({ mouseOver: '' })
}
this.scene.remove(mainAxleGroup)
this.scene.remove(gearGroup)
this.scene.remove(powerGroup)
}
} else {
this.scene.remove(mainAxleGroup)
this.scene.remove(gearGroup)
this.scene.remove(powerGroup)
}
if (!intersectsObject.length && !intersectsFlabellum.length) {
if (!this.state.mouseSelect) {
this.setState({ mouseOver: '', control: true })
} else {
this.setState({ mouseOver: '' })
}
}
}
鼠標點擊查看詳細信息烂瘫,鼠標按下的時候媒熊,對此時正處于選中狀態(tài)的部件進行動畫處理。對于信息量較少的部件坟比,需要給一個小彈窗來顯示信息即可芦鳍。
代碼如下:監(jiān)聽鼠標事件onClick。
// 點擊事件
this.clickEvent = (event) => {
// 點擊扇葉停止動畫葛账。點擊扇葉并且下一次不點擊扇葉開始動畫柠衅。
if (this.state.isClickLeaf) {
this.setState({ isClickLeaf: false })
this.animate()
}
if (this.state.mouseOver) {
if (this.state.mouseOver === '扇葉') {
cancelAnimationFrame(this.animateId)
$('.popup').css({
left: event.offsetX,
top: event.offsetY - $('.popup').height(),
})
} else if (this.state.mouseOver === '輪轂') {
$('.popup').css({
left: event.offsetX,
top: event.offsetY - $('.popup').height(),
})
} else if (this.state.mouseOver === '主軸') {
$('.popup').css({
left: event.offsetX,
top: event.offsetY - $('.popup').height(),
})
} else if (this.state.mouseOver === '齒輪結構') {
this.setState({ mouseSelect: '齒輪結構', control: true })
} else if (this.state.mouseOver === '發(fā)電機箱') {
this.setState({ mouseSelect: '發(fā)電機', control: true })
} else if (this.state.mouseOver === '偏航系統(tǒng)') {
this.setState({ mouseSelect: '偏航系統(tǒng)', control: true })
}
} else {
if (this.state.mouseSelect !== '齒輪結構' && this.state.mouseSelect !== '偏航系統(tǒng)' && this.state.mouseSelect !== '發(fā)電機') {
this.setState({ mouseSelect: '', control: true })
}
}
}
當所選部件信息量較大時,如下顯示籍琳。
結束語
web頁面因Canvas和Three.js在2D和3D方面的支持變得豐富多彩菲宴,妙趣橫生。生動豐富的數(shù)據(jù)展示趋急,方便了我們對風機的數(shù)據(jù)的實時監(jiān)測喝峦。
至此,本文和本系列文章已經(jīng)告一段落了宣谈,希望可以幫助讀者更加了解我們的孿生風機愈犹。有任何問題,歡迎聯(lián)系我們!關注一下漩怎,下次找我不迷路勋颖!如果覺得文章有可取之處,還請多多點贊勋锤!