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

歷史文章css

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

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

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

前情回顧: 上一篇文章中,我介紹了知識星空核心功能的實現方法:如何使用canvas模擬地圖的平移和縮放。在這篇文章中,咱們緊接着來講一說其餘一些精彩的功能。算法

如何對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的動畫實現。

點擊點位後的生成動畫分爲三個步驟: 這是原始沒有彈窗的狀態

1. 生成和當前點相同大小的圓

這裏須要用到上面所說的點擊事件,步驟以下: 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的事情

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:原本還想加入路徑的實現過程,以爲篇幅太長,路徑的實現仍是放在下一篇中。後續還會繼續探討點位生成背後的算法思路,盡情期待

相關文章
相關標籤/搜索