右上角需要從無的狀態撕開一個標記 , 且有動畫過程 , 上圖是實現的效果圖 , 不是gif
對這個翻角效果的難點在於沒有翻開的時候露出的是dom下面的內容 , 實現角度來說 純dom + css動畫的設計方案並沒有相出一個好的對策 ; 於是撿起了好久之前學的入門級別的canvas;
下面說一下實現思路:
- 動畫拆分 :
將此動畫分解成兩部分 , 一部分是翻頁出現的黑色三角區域 , 另一個是露出的橘色展示內容
對於橘色的展示內容區域相對好一些 , 因為是一個規則圖形 , 而黑色區域相對較難;
先從基礎canvas使用方法說起 :
1 2 3 |
<div class="container"> <canvas class="myCanvas" width="100" height="100"></canvas> </div> |
佈局如上 , 這裡要說一點踩過的坑是 , canvas必須要設定上width 與 height , 此處並非為css中的width與height;而是寫在dom上的屬性 ; 因為dom上的width與height標識了canvas的解析度(個人理解); 所以此canvas畫布解析度為100*100 , 而展示尺寸是可以通過css控制;
js中首先要做的是獲取canvas物件 ,
1 2 3 4 |
var canvas = document.querySelector('.myCanvas'); //獲取canvas對應dom var ctx = canvas.getContext('2d'); //此方法較為基礎 , 意為獲取canvas繪畫2d內容的工具(上下文) var cw = 100; //解析度 , 其實直接從dom上獲取可能更好些 var ch = 100; //解析度 , 其實直接從dom上獲取可能更好些 |
ctx這個繪畫上下文在這個教程中起到的作用至關重要 ; 它提供了非常強大的api , 比如用於畫線 , 填充 , 寫文字等 , 這樣看來理解為畫筆會更為簡明一些;
此處效果需要用到的api如下 ( 不做詳細解釋 , 可w3c自行查詢 );
1 2 3 4 5 6 7 8 9 10 11 12 |
ctx.save() //儲存上下文狀態 (比如畫筆尺寸 顏色 旋轉角度) ctx.restore() //返回上次儲存的上下文狀態 ctx.moveTo(x,y) //上下文移動到具體位置 ctx.lineTo(x,y) //上下文以劃線的形式移動到某位置 ctx.stroke() // 畫線動作 ctx.quadraticCurveTo() //上下文(畫筆)按貝塞爾曲線移動(簡單理解為可控的曲線即可) ctx.arc() //畫圓 ctx.beginPath() //開啟新的畫筆路徑 ctx.closePath() //關閉當前畫筆路徑 ctx.createLinearGradient() //建立canvas漸變物件 ctx.fill() //對閉合區域進行填充 ctx.globalCompositeOperation //畫筆的重疊模式 |
可能方法列舉的不夠詳盡 , 見諒.
首先是繪製黑色翻出的部分 , 圖形分解為如下幾部分(請根據上圖腦補)
- 左上角向右下的半弧 ╮
- 然後是豎直向下的豎線 |
- 然後是向右的半圓 ╰
- 再然後是向右的橫線
- 接著還是向右下的半弧 ╮
- 最後是將線連線會起點
於是第一步 我們要先將畫筆移動到 起始位置
1 |
ctx.moveTo(50,0); |
然後
1 |
ctx.quadraticCurveTo(55 , 5 , 55 , 25); // 可以理解為從(50,0)這個點劃線到(55,25)這個點 , 中間會受到(55,5)這個點將直線想磁鐵一樣"吸"成曲線; |
於是第一個向右下的半弧完成 , 此時canvas上沒有任何繪製內容 , 因為還沒有執行過繪製方法例如stroke或fill,
接下來直線向下就是簡單的移動
1 |
ctx.lineTo(55 , 40); |
這個時候我們接下來應該畫向右的半圓 , 這個時候再用貝塞爾曲線繪製 實在有些不太合適 , 因為從圖上來看 , 這裡完全是1/4的圓 , 所以要使用canvas提供的畫圓的api
1 |
ctx.arc(60 , 40 , 5 , Math.PI , Math.PI / 2 , true); |
上述畫圓的程式碼意為 : 以(60,40)點為圓心 , 5為半徑 , 逆時針從 180度繪製到90度 , 180度就是圓心的水平向左 到達點(55,40) , 與上一步連線上 , 然後又因為螢幕向下為正 , 90度在圓心正下方 , 所以繪製出此半圓
於是按照相同的步驟 水平向右
1 |
ctx.lineTo(75 , 45); |
然後再次使用貝塞爾曲線用第一步的思路畫出向右下的弧;
1 |
ctx.quadraticCurveTo( 95 , 45 , 100 , 50 ); |
同理 上述貝塞爾曲線可以理解為一條從( 75 , 45 ) 到 ( 100 , 50 )的線被 ( 95 , 45 )”吸”成曲線
最後連結起點 , 閉合繪畫區域
1 |
ctx.lineTo(50 , 0); |
這個時候黑色區域的翻頁就畫完了 , 然後此時開始填充顏色 ;
1 2 3 4 |
var gradient = ctx.createLinearGradient(50 , 50 , 75 , 75); gradient.addColorStop(0 , '#ccc'); gradient.addColorStop(0.7 , '#111'); gradient.addColorStop(1 , '#000'); |
我們通過上述程式碼建立一個 從( 50 , 50 )點到(75 , 75)點的線性漸變 , 顏色從 #ccc 到 #111 到 #000 ; 建立高光效果;
然後填充:
1 2 |
ctx.fillStyle = gradient; ctx.fill(); |
於是翻頁效果的一半就算完成了。
至此 , 我要說一點我領悟的canvas的繪畫”套路”;
對於上述教程中 , 有一步我們使用了一個詞叫做 閉合 , 閉合的概念在canvas中是真是存在的 , 對於fill方法來說 填充的區間是有一個空間尺寸才可以的 , 比如我們繪畫的這個黑色的三角形 , 加入我們最後沒有將終點與起點相連線 , 同樣canvas會自動幫我們連結最後一筆繪畫的位置到起點 , 強制行程閉合空間 , 而這樣我們想再多畫幾個新的閉合空間就麻煩了 , 所以canvas提供瞭如下api 新建閉合路徑:
1 2 |
ctx.beginPath(); //新建路徑 ctx.closePath(); //閉合路徑 |
所以對於我們接下來要繪製右上角橘色區域來說 , 我們在繪製黑色區域之前首先要做的是
1 2 |
ctx.beginPath(); ... |
然後在fill之前 我們應該
1 |
ctx.closePath(); |
也就是說beginPath 到 closePath之間標識著我們自己的一個完整的繪畫階段.
那麼接下來繪製右上角的橘色區域就簡單很多了:
1 2 3 4 5 6 7 8 |
ctx.beginPath(); ctx.moveTo(50,0); ctx.lineTo(100,50); ctx.lineTo(100,0); ctx.lineTo(50,0); ctx.closePath(); ctx.fillStyle = '#ff6600'; ctx.fill(); |
於是右上角的橘色區域我們就繪製完成了;
文字繪製
接下來繪製”new” , 實際上是使用canvas簡單的文字繪製 , 程式碼如下:
1 2 3 4 5 6 7 8 9 10 |
var deg = Math.PI / 180; ctx.globalCompositeOperation = 'source-atop'; //canvas層疊模式 ctx.beginPath(); ctx.font = '14px Arial'; //設定字型大小 字型 ctx.textAlign = 'center'; // 字型對齊方式 ctx.translate(78 , 22); // 移動canvas畫布圓點 ctx.rotate(45 * deg); // 旋轉畫布 ctx.fillStyle = '#fff'; // 設定文字顏色 ctx.fillText('NEW' , 0 , 0); //文字繪製動作 ctx.closePath(); |
對於上述程式碼中 , 文字的相關api是屬於沒有難度的 , 只是設定而已 , 需要理解的部分在於 translate和rotate,
這兩個方法中 translate的意思為移動canvas畫布的( 0 , 0 )點到 (78,22),然後旋轉45度, 再將文字渲染在原點 , 實際就是 ( 78 , 22 ) 這個點上, 此時我們對canvas的畫筆做出了非常大的修改
比如我們修改了旋轉角度以及畫布圓點 , 這種操作或許只在我們需要繪製傾斜的new 的時候需要 , 後期可能就不需要使用了 ,
還好canvas的畫筆是存在”狀態”的, 通過ctx.save();可以儲存當前畫筆的狀態 , 通過ctx.restore();可以恢復到上次畫筆儲存的狀態.
於是我個人理解到 , 在開發canvas動畫時 , 一個較好的習慣就是 , 在beginPath之前先ctx.save();儲存畫筆狀態 , 在closePath後ctx.restore();恢復之前的畫筆狀態 , 這樣我們的每一個繪製階段對於畫筆的修改都將是不會有影響的.( 個人經驗 )
1 |
ctx.globalCompositeOperation = 'source-atop'; //canvas層疊模式 |
程式碼中這部分是指 我們繪製的文字new 與 橘色三角形區域的重疊關係 , 此方法取值較多 , 此處不做過多介紹 , source-atop值可以使重疊區域保留 , 新繪製的內容在重疊區域以外的部分消失 , 以此達到new在裡面的效果
到這裡我們就開發好了翻角效果的完全展示的狀態 , 那麼如何讓這個區域動起來呢?
此處需要使用h5提供的用於刷幀的函式 requestAnimationFrame ;
此方法可簡單理解為 16毫秒的定時器 , 但是厲害的是可以再各個環境中自動匹配到可達到的相對順暢的幀率 , 實際並不是定時器哈~
我們需要在這個迴圈執行的函式中 , 將上述的繪製內容重複繪製 , 例如 :
1 2 3 4 5 6 7 8 9 |
function draw(){ drawMethod(); //繪製三角等內容 window.requestAnimationFrame(function(){ draw(); }) } function drawMethod(){ //... } |
這樣我們就可以達到刷幀的效果了 , 於是接著我們要做的就是控制繪製時各個數值的引數.
比如我們是以 (50,0)為起點 , ( 100 , 50 )為終點這樣的兩個移動點為繪製標記的 , 如果我們將兩個點進行儲存 , 並且每次執行drawMethod的時候更新點的位置 , 然後清空canvas ,再繪製新的點 那麼就可以達到canvas動起來的目的了;
在上面的demo連結中 , 自己定義了一個速度與加速度的關係 , 比如每次繪製一次canvas後 , 將儲存的點座標進行增加一個speed值 , 然後speed值也增加 , 這樣speed對應的概念就是速度 , 而speed的增加值對應的就是加速度. 所以就呈現了一種加速運動的狀態;
以上內容純屬個人理解內容 , 若果有哪裡理解錯了 歡迎各位大大指點 , 另demo連結失效可私信.