帶着canvas去流浪系列之七 繪製水球圖

【摘要】 用原生canvasAPI實現百度echartswebpack

示例代碼託管在:http://www.github.com/dashnowords/blogsgit

一. 任務說明

使用原生canvasAPI繪製水球圖,這將是一個很是有意思的挑戰任務。水球圖是一種常見的加載動畫,屬於擴展圖形,在echarts中使用時須要下載擴展庫(同爲擴展庫的還包括文字雲插件和地圖插件,項目地址爲https://github.com/ecomfe/echarts-liquidfill)。github

pic1.gif

二. 重點提示

水球圖的繪製有如下幾個難點:web

  1. 水波的繪製canvas

    水波的繪製其實是運用簡諧振動公式來模擬的,也就是x = A*(wt +φ),其中振幅A決定了水波的波紋高低,角頻率w決定了水波的快慢,相位φ決定了初始位移差,再加上一些y軸方向的位移誤差和顏色的差別,就能夠模擬出不一樣的水波,接着只須要在幀動畫中不斷改變φ並重繪曲線,就能夠模擬出水波效果了。瀏覽器

  2. 球形剪裁區域echarts

    水波的範圍是不能流出球形的外輪廓的,此處的作法是在繪製水波以前,先使用context.clip( )方法將水波的可見繪圖區域控制在水球以內便可,若是還有水球外的圖形須要繪製,記得在每一幀繪製完水波後調用context.restore( )取消掉以前的剪裁。dom

  3. 文字的繪製動畫

    若是隻是繪製漂浮於水球圖之上的文字,是比較容易實現的,可是若是想要實現一些細節更豐富的效果,並不那麼容易。咱們指望實現的效果是,當文字未被水波浸入時,顯示水紋的藍色,而被水浸潤的部分顯示爲白色,這樣看起來更加生動。可是繪製起來卻並不容易,若是將文字繪製成藍色,那麼被水淹沒的部分就會消失在水紋中,若是繪製成白色,那麼水紋高度較小時,會徹底看不到文字。那麼這樣的渲染文字要如何實現呢?ui

三. 示例代碼

let options = {
   value:0,
   a:20,//振幅
   pos:[300,300],//水球圖位置
   r:160,//水球圖半徑
   color:['#2E5199','#1567c8','#1593E7','#42B8F9']//水紋顏色
};

start(options);

/**
* 繪製水球圖
*/
function start(options) {
   //移動繪圖座標至水球圖左邊界點
   context.translate(options.pos[0],options.pos[1]);
   context.font = 'bold 60px Arial';
   context.textAlign='center';
   context.textBaseLine = 'baseline';
   //計算水球圖繪圖數據
   createParams(options);
   //開啓幀動畫
   requestAnimationFrame(startAnim);
}

//生成水波動畫參數,位置座標公式爲 y = A * (wt + φ)
function createParams(options) {
   options.w = [];//存儲水波的角速度
   options.theta = [];//存儲每條水波的位移
   for(let i = 0; i < 4; i++){
     options.w.push(Math.PI /(100 + 20*Math.random()));
     options.theta.push(20*Math.random());
   }
}

//繪製水波線
function drawWaterLines(options) {
  let offset;
  let A = options.a;//正弦曲線振幅
  let y,x,w,theta;
  let r = options.r;
  //遍歷每一條水紋理
  for(let line = 0; line < 4; line++){ 
    context.save();
    //每次繪製時水波的偏移距離
    theta = Math.random();
    offset = r + A / 2  -  (r*19/8 + A) * (options.value / 100 ) + line * r/12;
    //獲取正弦曲線計算參數
    w = options.w[line];
    theta = options.theta[line];
    context.fillStyle = options.color[line];
    context.moveTo(0,0);
    context.beginPath(); 
    //以0.1爲步長繪製正弦曲線
    for(x = 0; x <= 2*r; x+=0.1){
       y = A * Math.sin(w * x + theta) + offset;
       //繪製點
       context.lineTo(x,y);
    }
     //繪製爲超出水球範圍的封閉圖形
     context.lineTo(x,r);
     context.lineTo(x - 2 * r,r);
     context.lineTo(0, A * Math.sin(theta) - options.height);
     context.closePath();
     //填充封閉圖形獲得一條水波
     context.fill();
     //截取水波範圍,繪製文字(此處將在後文解釋)
     context.clip();
     context.fillStyle = 'white';
     context.fillText(parseInt(options.value,10) + '%',options.r + 10,10);
     context.restore();
  }
}

//繪製最底層文字
function drawText1(options) {
   context.fillStyle = options.color[0];
   context.fillText(parseInt(options.value,10) + '%',options.r + 10,10);
}

//幀動畫循環
function startAnim() {
   //用位移變化模擬水波
   options.theta = options.theta.map(item=>item-0.03);
   //用百分比進度計算水波的高度
   options.value += options.value > 100 ? 0:0.1;
   context.save();
   resetClip(options);//剪切繪圖區
   drawText1(options);//繪製藍色文字
   drawWaterLines(options);//繪製水波線
   context.restore();
   requestAnimationFrame(startAnim);
}

/**設置水球範圍爲剪裁區域
*(本例中並無水球之外的部分須要繪製,實際上這裏不須要加入幀動畫循環中,只須要在開頭設置一次便可。)
*/
function resetClip(options) {
  let r = options.r;
  context.strokeStyle = '#2E5199';
  context.fillStyle = 'white';
  context.lineWidth = 10;
  context.beginPath();
  context.arc(r, 0, r + 10, 0, 2*Math.PI, false);
  context.closePath();
  context.fill();
  context.stroke();
  context.beginPath();
  context.arc(r, 0, r, 0, 2*Math.PI, true);
  context.clip();
}

瀏覽器中可查看效果:

pic2.PNG

四. 文字淹水效果的實現

文字淹水效果的繪製其實是按照以下思路來進行的:

  1. 首先繪製與最上層水紋顏色一致的文字,這樣在被水淹沒以前,文字均可以以可見的顏色顯示。

  2. 在繪製水波的過程當中,連線完成後調用context.clip( )方法將繪圖區域剪裁爲全部浸水部分,此時再將填充色設置爲白色,接着在同一個位置渲染文字,這樣渲染出的白色文字不會超出水紋的範圍,那麼水紋以外的文字的藍色部分也就被保存在畫布上了。

  3. 爲了不文字中白色的部分被下一層水紋繪製時截斷,咱們須要在每一層水紋繪製後,都重複步驟2,將該層水紋到水球底部的全部範圍設置爲剪裁區域,而後繪製該層水紋之內的白色文字部分,這樣當幾層水紋都繪製完畢後,文字淹水的部分就都會被染成白色。

  4. 在這樣的繪製方法中,文字的最終效果至關因而逐層繪製出來的片斷拼接起來的,每次繪製中能被保存到最後的部分,都只有和當前層的水紋相交的部分。

若是咱們將每一層文字的繪製顏色修改一下,就比較容易理解繪製過程:

pic3.PNG

五. 關於canvas抗鋸齒

若是仔細查看上面的水球外圓,會發現水球圖的外側不是很平整,看起來會有不少鋸齒。查到的方法大可能是將畫布畫布尺寸(canvas.height,canvas.width)調整爲元素尺寸(CSS中設置的canvas元素的尺寸)的3-4倍,但願利用縮放來達到抗鋸齒的做用,但實測的結果卻並無明顯改進,利用畫布尺寸來縮放在解決圖像和填充模糊的時候效果較好,但在抗鋸齒方面的做用彷佛與線條自己的尺寸仍有關係,不是一種絕對有效的方案。另外一種較爲有效的方案,是在繪製外圓時增長2px-4px的深色陰影,在視覺上能夠很好地弱化鋸齒感。

//在繪製外圓以前添加以下代碼   
  context.shadowColor = '#2E5199';
  context.shadowBlur = 2;
  context.shadowOffsetX = 0;
  context.shadowOffsetY = 2;

六. 小結

至此,咱們在這個系列中完成了全部基本圖表的原生API繪製,一些相對高級的圖表,其繪製過程並不必定很複雜,好比矩形樹圖,繪製起來實際上都是矩形方塊,但卻有助於咱們以某種更直觀更具備表現力的方式來觀察數據,例如可視化呈現webpack的打包結果。數據可視化的基本任務就是讓數據變得可視,這須要咱們爲想觀察的數據選出恰當的表現方式,這不是純粹靠技術可以達到的,也須要一些藝術細胞和想象力。但不管如何,這都是一個值得研究的有趣的方向。

demo.rar

md原文.rar

做者:大史不說話

相關文章
相關標籤/搜索