歷史文章css
歡迎前往集智學園官網體驗web
前情回顧: 上一篇文章中,我介紹了知識星空核心功能的實現方法:如何使用canvas模擬地圖的平移和縮放。在這篇文章中,咱們緊接着來講一說其餘一些精彩的功能。算法
canvas實際上是一張大畫布,當你在上面開始繪圖以後,元素就和畫布變成了一個總體。而咱們的需求是須要對繪製的每一個點都要設置事件(這裏主要指點擊事件)。canvas
各類地圖都是使用dom生成地圖上的標記(marker),因此能夠很方便地對標記綁定事件。但在咱們這裏,全部的點都由是canvas繪製,並無獨立的「元素」概念。因此想要爲某個元素添加事件就成了一件比較蛋疼的事情。瀏覽器
這裏使用了一個比較笨的辦法,就是監聽整個畫布,而後去比對這個點擊時刻的位置,和每一個圓心
的距離是否小於這個圓的半徑。bash
聽上去運算量就很大,那最終效果怎麼樣呢?咱們來試一試dom
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時,說明當前選中了一個點。ide
接下去還想加一個需求:監聽到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
的動畫實現。
點擊點位後的生成動畫分爲三個步驟: 這是原始沒有彈窗的狀態
這裏須要用到上面所說的點擊事件,步驟以下: a. 點擊某個點 b. 把這個點移動到視圖的中心位置 c. 在當前位置生成一個和這個圓半徑相同的圓形窗口
點擊一個圓獲取它的參數,在上一個模塊中已經實現。
生成圓的部分也比較簡單,只要獲取到當前點擊的是哪一個圓,根據當前圓的半徑,在屏幕中心
生成一個和它徹底重疊的圓就能夠。可使用一個固定的dom,控制它是否顯示,以及根據選中圓的半徑控制它的寬高。
因此重點是點擊某個點後,把這個圓移動到視圖中心的過程應該如何實現。
上一篇文章集智學園知識星空——前端技術實現分析(一)中已經說到了移動地圖的實現方式。核心是transform(x, y)
函數,參數x, y爲橫縱座標分別要移動的距離。確定不能直接把中心座標和當前點的位置作差直接傳入,不然就是一個突變的現象,咱們是要作一個絲滑的移動動畫實現最終的功能。
因此須要把中心座標
和當前點
的座標作差後,分段傳入tranform函數
。至於分幾段比較合適,就須要把它做爲一個超參細緻地進行調整。
最終我是調整出了一種計算分段的方法,讓分段參數和移動距離成負相關,這樣就可讓移動距離長的點移動得快一些,而移動距離短的過程進行得慢一些。
<!--point爲要移動到中心位置的點-->
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的事情
在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:原本還想加入路徑的實現過程,以爲篇幅太長,路徑的實現仍是放在下一篇中。後續還會繼續探討點位生成背後的算法思路,盡情期待