集智學園知識星空——前端技術實現分析(二)

如意同學發表於2019-04-02

歷史文章

集智學園知識星空——產品介紹篇

集智學園知識星空——前端技術實現分析(一)

歡迎前往集智學園官網體驗

前情回顧: 上一篇文章中,我介紹了知識星空核心功能的實現方法:如何使用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:本來還想加入路徑的實現過程,覺得篇幅太長,路徑的實現還是放在下一篇中。後續還會繼續探討點位生成背後的演算法思路,盡情期待

相關文章