【帶著canvas去流浪(7)】繪製水球圖

来源:https://www.cnblogs.com/dashnowords/archive/2019/04/11/10692243.html
-Advertisement-
Play Games

示例代碼托管在: "http://www.github.com/dashnowords/blogs" 博客園地址: "《大史住在大前端》原創博文目錄" 華為雲社區地址: "【你要的前端打怪升級指南】" [TOC] 一. 任務說明 使用原生 繪製水球圖,這將是一個非常有意思的挑戰任務。水球圖是一種常見 ...


目錄

示例代碼托管在:http://www.github.com/dashnowords/blogs

博客園地址:《大史住在大前端》原創博文目錄

華為雲社區地址:【你要的前端打怪升級指南】

一. 任務說明

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

二. 重點提示

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

  1. 水波的繪製

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

  2. 球形剪裁區域

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

  3. 文字的繪製

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

三. 示例代碼

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();
}

瀏覽器中可查看效果:

四. 文字淹水效果的實現

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

  1. 首先繪製與最上層水紋顏色一致的文字,這樣在被水淹沒之前,文字都可以以可見的顏色顯示。
  2. 在繪製水波的過程中,連線完成後調用context.clip( )方法將繪圖區域剪裁為所有浸水部分,此時再將填充色設置為白色,接著在同一個位置渲染文字,這樣渲染出的白色文字不會超出水紋的範圍,那麼水紋之外的文字的藍色部分也就被保存在畫布上了。
  3. 為了避免文字中白色的部分被下一層水紋繪製時截斷,我們需要在每一層水紋繪製後,都重覆步驟2,將該層水紋到水球底部的所有範圍設置為剪裁區域,然後繪製該層水紋以內的白色文字部分,這樣當幾層水紋都繪製完畢後,文字淹水的部分就都會被染成白色。
  4. 在這樣的繪製方法中,文字的最終效果相當於是逐層繪製出來的片段拼接起來的,每次繪製中能被保存到最後的部分,都只有和當前層的水紋相交的部分。

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

五. 關於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的打包結果。數據可視化的基本任務就是讓數據變得可視,這需要我們為想觀察的數據選出恰當的表現方式,這不是純粹靠技術能夠達到的,也需要一些藝術細胞和想象力。但無論如何,這都是一個值得研究的有趣的方向。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard" bundle:nil]; UIViewController *vc = [storyboard instantiateViewControlle... ...
  • 原生Ajax發送請求: 1.創建XMLhttpRequest對象 ie中為ActiveXobject("Microsoft.XMLHTTP") 早期的ie瀏覽器 2.準備發送 3.執行發送動作 4.指定回調函數 案例: 1.html文件 2.PHP文件 1.get get請求參數在url中傳遞 2. ...
  • 公司的網頁代碼需要測試,因後端擋板工具問題只能在瀏覽器里進行。但是在瀏覽器里打開時比在客戶端打開時少了一些必要的參數(放在PJF里)。需要在初始化時放進去。不可能把所有頁面改一下吧。最後我發現所有頁面都載入了sealUtils.js工具函數。於是我寫了一個自執行函數在放在sealUtils.js前。 ...
  • fullpage 全屏插件 全屏滾動效果,原生js也很好實現,主要是用 mousewheel 滑鼠滾輪滾動事件, 來判斷上滾動還是下滾動,之後設置每次滾動的高度為屏幕的高度即可。但是,雖然效果簡單,但是相容性很差,要做很多相容處理及比較麻煩啦! fullPage.js 是一個基於 jQuery 的插 ...
  • 盒子模型 1. 盒子模型的概念 2. 高和寬的設置 3. 邊框的設置 4. 內邊距的設置 5. 外邊距的設置 6. 盒子的計算 7. 元素顯示的方式 盒子模型: border邊框,margin外邊距,padding內部距,content內容,width寬度。 高度: height: 長度值|百分比| ...
  • 使用插件 formidable > npm i formidable 後臺代碼 import formidable from 'formidable' 輸出結果: 圖片存儲地方: Node.js的Formidable模塊的使用 1) 創建Formidable.IncomingForm對象 var f ...
  • 這篇隨筆繼續來認識HTML標簽。這次隨筆主要是對<table>標簽的認識和最近我學習到的一些標簽來和大家分享。 一、<table>標簽 <table>標簽的作用主要是定義HTML表格,<table>內也分頭<thead>和主體<tbody>,而簡單的HTML表格由table元素以及一個或者多個tr, ...
  • import React, { Component } from 'react' import ReactDOM from 'react-dom' class App extends Component { constructor(props) { super(props); this.state ... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...