歷史文章
歡迎前往集智學園官網體驗
前情回顧: 上一篇文章中,我介紹了知識星空核心功能的實現方法:如何使用canvas模擬地圖的平移和縮放。在這篇文章中,我們緊接著來說一說其他一些精彩的功能。
如何對canvas中一個元素設定事件
canvas其實是一張大畫布,當你在上面開始繪圖之後,元素就和畫布變成了一個整體。而我們的需求是需要對繪製的每個點都要設定事件(這裡主要指點選事件)。
各種地圖都是使用dom生成地圖上的標記(marker),所以可以很方便地對標記繫結事件。但在我們這裡,所有的點都由是canvas繪製,並沒有獨立的“元素”概念。所以想要為某個元素新增事件就成了一件比較蛋疼的事情。
這裡使用了一個比較笨的辦法,就是監聽整個畫布,然後去比對這個點選時刻的位置,和每個圓心
的距離是否小於這個圓的半徑。
聽上去運算量就很大,那最終效果怎麼樣呢?我們來試一試
clickHandler: e => {
let { x, y } = { e.clientX, e.clientY }
let point = null
this.pointList.forEach(ele => {
<!--計算當前滑鼠點選的位置和圓心的距離,並與半徑比較-->
if (Math.sqrt(Math.pow(x - ele.x), 2)) + Math.pow((y - ele.y), 2)) <= ele.r) {
//滑鼠選中了這個圓
point = ele
}
})
}
複製程式碼
這樣看下來感覺也還行,只是在每次點選的時候去遍歷一遍所有的點。當clickHandler
函式返回不為null時,說明當前選中了一個點。
接下去還想加一個需求:監聽到hover事件,hover到一個點上之後改變滑鼠樣式。
emm...
按照上面的思路應該是這樣寫
hoverhandler: e => {
let { x, y } = { e.clientX, e.clientY }
let point = null
this.pointList.forEach(ele => {
<!--計算當前滑鼠點選的位置和圓心的距離,並與半徑比較-->
if (Math.sqrt(Math.pow(x - ele.x), 2)) + Math.pow((y - ele.y), 2)) <= ele.r) {
//滑鼠hover到了這個圓
point = ele
}
})
}
複製程式碼
上面的函式本身沒問題,問題就在於呼叫頻次上,對畫布監聽mousemove
事件的話,會以非常高的頻率呼叫這個函式。假設滑鼠一直在移動,1s中觸發5次mousemove
事件的話,那1s內就會遍歷比較所有的點5遍。這就就會比較明顯地對效能產生影響了。
這該怎麼辦?
後來想了一個優化的辦法:就是隻去遍歷當前檢視
中的點。即把點位位置和當前瀏覽器視窗去比較,如果在螢幕之外的話,就不用去和這個點去比。
另外還可能對點的大小進行過濾,半徑太小的點也不用去比較,對最後效果影響並不大。
最後實現是這樣寫。
hoverhandler: e => {
let { x, y } = { e.clientX, e.clientY }
let point = null
this.pointList.forEach(ele => {
<!--加上過濾條件-->
if (ele.x- ele.value <= window.width && ele.x + ele.value >= 0 && ele.y - ele.value <= window.height && ele.y + ele.value >= 0 && ele.value > 15) {
<!--計算當前滑鼠點選的位置和圓心的距離,並與半徑比較-->
if (Math.sqrt(Math.pow(x - ele.x), 2)) + Math.pow((y - ele.y), 2)) <= ele.r) {
//滑鼠hover到了這個圓
point = ele
}
}
})
}
複製程式碼
到這裡就算是完成了對畫布中元素設定事件的過程。如果有需求需要加上的別的事件,也可以使用這個思路去寫。
(我覺得還不是最優解決方案,大佬們有想法歡迎和我討論)
接下去我們來說一說視窗的彈出動畫部分的實現
視窗彈出動畫
網站有很多的動畫效果,從效能上考慮,原則是能用css實現的就絕不用js,實現效果如下:
看起來好像很複雜,其實都是由基本的keframe
組成的,原理一點都不難。這裡結合實際的業務邏輯,給大家複習下css3
的動畫實現。
點選點位後的生成動畫分為三個步驟: 這是原始沒有彈窗的狀態
1. 生成和當前點相同大小的圓
這裡需要用到上面所說的點選事件,步驟如下: a. 點選某個點 b. 把這個點移動到檢視的中心位置 c. 在當前位置生成一個和這個圓半徑相同的圓形視窗
點選一個圓獲取它的引數,在上一個模組中已經實現。
生成圓的部分也比較簡單,只要獲取到當前點選的是哪個圓,根據當前圓的半徑,在螢幕中心
生成一個和它完全重疊的圓就可以。可以使用一個固定的dom,控制它是否顯示,以及根據選中圓的半徑控制它的寬高。
所以重點是點選某個點後,把這個圓移動到檢視中心的過程應該如何實現。
上一篇文章集智學園知識星空——前端技術實現分析(一)中已經說到了移動地圖的實現方式。核心是transform(x, y)
函式,引數x, y為橫縱座標分別要移動的距離。肯定不能直接把中心座標和當前點的位置做差直接傳入,否則就是一個突變的現象,我們是要做一個絲滑的移動動畫實現最終的功能。
所以需要把中心座標
和當前點
的座標做差後,分段傳入tranform函式
。至於分幾段比較合適,就需要把它作為一個超參細緻地進行調整。
最終我是調整出了一種計算分段的方法,讓分段引數和移動距離成負相關,這樣就可以讓移動距離長的點移動得快一些,而移動距離短的過程進行得慢一些。
<!--poin為要移動到中心位置的點-->
panToCenter (point) {
<!--選中的點和中心點之間的距離-->
let dis = Math.sqrt(Math.pow(point.x - window.center, 2) + Math.pow(point.y - window.center, 2));
<!--根據距離調整出的一個函式,讓分段和距離成負相關-->
let count = f(dis)
//每個分段的距離:perLength = length / count
let perLength = { x: (point.x - window.center) / count, y: (point.y - window.center) / count };
let panToTime = setInterval(() => {
transform(-perLength.x, -perLength.y);
count--;
if (count <= 0) {
clearInterval(panToTime);
this.panToTime = null;
}
}, 20);
}
複製程式碼
到為止這裡為止我們就完成了點位的移動和視窗的開啟。
接下去就是css的事情
2. 圓半徑變大,隨後變成矩形視窗,並顯示內容
在keyframe
裡面定義一個初始狀態,中間狀態和最終狀態,設定動畫時間,動畫效果等引數
@keyframes openUp {
<!--初始狀態是一個園-->
from {
opacity: 0.7;
border-radius: 50%;
}
<!--放大過程中間狀態依舊是圓-->
50% {
opacity: 0.8;
border-radius: 50%;
}
<!--最後變成一個矩形-->
100% {
opacity: 0.95;
border-radius: 20px;
height: 90%;
width: 60%;
}
}
.openUp {
-webkit-animation-timing-function: ease-in-out;
-webkit-animation-duration: 0.5s;
-webkit-animation-name: openUp;
animation-timing-function: ease-in-out;
animation-name: openUp;
animation-duration: 0.5s;
}
複製程式碼
最後在視窗中載入內容即可
ps:本來還想加入路徑的實現過程,覺得篇幅太長,路徑的實現還是放在下一篇中。後續還會繼續探討點位生成背後的演算法思路,盡情期待