【带着canvas去流浪(7)】绘制水球图

示例代码托管在:http://www.github.com/dashnowords/blogs
博客园地址:《大史住在大前端》原创博文目录
华为云社区地址:【你要的前端打怪升级指南】
一. 任务说明
使用原生canvasAPI绘制水球图,这将是一个非常有意思的挑战任务。水球图是一种常见的加载动画,属于扩展图形,在echarts中使用时需要下载扩展库(同为扩展库的还包括文字云插件和地图插件,项目地址为https://github.com/ecomfe/echarts-liquidfill)。

二. 重点提示
水球图的绘制有以下几个难点:
水波的绘制
水波的绘制实际上是运用简谐振动公式来模拟的,也就是
x = A*(wt +φ),其中振幅A决定了水波的波纹高低,角频率w决定了水波的快慢,相位φ决定了初始位移差,再加上一些y轴方向的位移偏差和颜色的差异,就可以模拟出不同的水波,接着只需要在帧动画中不断改变φ并重绘曲线,就可以模拟出水波效果了。球形剪裁区域
水波的范围是不能流出球形的外轮廓的,此处的做法是在绘制水波之前,先使用
context.clip( )方法将水波的可见绘图区域控制在水球之内即可,如果还有水球外的图形需要绘制,记得在每一帧绘制完水波后调用context.restore( )取消掉之前的剪裁。文字的绘制
如果只是绘制漂浮于水球图之上的文字,是比较容易实现的,但是如果想要实现一些细节更丰富的效果,并不那么容易。我们期望实现的效果是,当文字未被水波浸入时,显示水纹的蓝色,而被水浸润的部分显示为白色,这样看起来更加生动。但是绘制起来却并不容易,如果将文字绘制成蓝色,那么被水淹没的部分就会消失在水纹中,如果绘制成白色,那么水纹高度较小时,会完全看不到文字。那么这样的渲染文字要如何实现呢?
三. 示例代码
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();
}
浏览器中可查看效果:

四. 文字淹水效果的实现
文字淹水效果的绘制实际上是按照如下思路来进行的:
- 首先绘制与最上层水纹颜色一致的文字,这样在被水淹没之前,文字都可以以可见的颜色显示。
- 在绘制水波的过程中,连线完成后调用
context.clip( )方法将绘图区域剪裁为所有浸水部分,此时再将填充色设置为白色,接着在同一个位置渲染文字,这样渲染出的白色文字不会超出水纹的范围,那么水纹之外的文字的蓝色部分也就被保存在画布上了。 - 为了避免文字中白色的部分被下一层水纹绘制时截断,我们需要在每一层水纹绘制后,都重复步骤2,将该层水纹到水球底部的所有范围设置为剪裁区域,然后绘制该层水纹以内的白色文字部分,这样当几层水纹都绘制完毕后,文字淹水的部分就都会被染成白色。
- 在这样的绘制方法中,文字的最终效果相当于是逐层绘制出来的片段拼接起来的,每次绘制中能被保存到最后的部分,都只有和当前层的水纹相交的部分。
如果我们将每一层文字的绘制颜色修改一下,就比较容易理解绘制过程:

五. 关于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的打包结果。数据可视化的基本任务就是让数据变得可视,这需要我们为想观察的数据选出恰当的表现方式,这不是纯粹靠技术能够达到的,也需要一些艺术细胞和想象力。但无论如何,这都是一个值得研究的有趣的方向。
【带着canvas去流浪(7)】绘制水球图的更多相关文章
- 【带着canvas去流浪】(2)绘制折线图
目录 一. 任务说明 二. 重点提示 三. 示例代码 3.1 一般折线图 3.2 用贝塞尔曲线绘制平滑折线图 四. 大数据量场景 示例代码托管在:https://github.com/dashnowo ...
- 带着canvas去流浪系列之二 绘制折线图
[摘要] 用canvasAPI实现echarts简易图表 示例代码托管在:http://www.github.com/dashnowords/blogs 一. 任务说明 使用原生canvasAPI绘制 ...
- 带着canvas去流浪系列之七 绘制水球图
[摘要] 用原生canvasAPI实现百度echarts 示例代码托管在:http://www.github.com/dashnowords/blogs 一. 任务说明 使用原生canvasAPI绘制 ...
- 【带着canvas去流浪(6)】绘制雷达图
目录 一. 任务说明 二. 重点提示 三. 示例代码 示例代码托管在:http://www.github.com/dashnowords/blogs 博客园地址:<大史住在大前端>原创博文 ...
- 带着canvas去流浪系列之六 绘制雷达图
[摘要] 用canvas原生API实现百度Echarts基本图表. 示例代码托管在:http://www.github.com/dashnowords/blogs 一. 任务说明 使用原生canvas ...
- 【带着canvas去流浪(5)】绘制K线图
目录 一. 任务说明 二. 重点提示 三. 示例代码 示例代码托管在:http://www.github.com/dashnowords/blogs 博客园地址:<大史住在大前端>原创博文 ...
- 带着canvas去流浪系列之五 绘制K线图
[摘要] 用canvas原生API实现百度Echarts 示例代码托管在:http://www.github.com/dashnowords/blogs 一. 任务说明 使用原生canvasAPI绘制 ...
- 【带着canvas去流浪(4)】绘制散点图
目录 一. 任务说明 二. 重点提示 三. 示例代码 四.散点hover交互效果的实现 4.1 基本算法 4.2 参考代码 4.3 Demo中的小问题 示例代码托管在:http://www.githu ...
- 【带着canvas去流浪】 (3)绘制饼图
目录 一. 任务说明 二. 重点提示 三. 示例代码 四. hover高亮的实现思路 示例代码托管在:http://www.github.com/dashnowords/blogs 博客园地址:< ...
随机推荐
- Linux文件系统选择
通过综合使用多种标准文件系统Benchmarks对Ext3, Ext4, Reiserfs, XFS, JFS, Reiser4的性能测试对比,对不同应用选择合适的文件系统给出以下方案,供大家参考.文 ...
- 一步一步理解 python web 框架,才不会从入门到放弃 -- 简单登录页面
上一节,我们基本了解了 Django 的一些配置,这一节,我们将通过一个简单的登录页面,进一步学习 Django 的使用. 新建项目 首先,新建一个 Django 项目,记得别弄错了哦. settin ...
- Java开发者必备的10大学习网站,送给入门学习java的你,请收下!
作为开发者来说,必备的除了对编码的热情还要有自己的一套技巧,另外不可缺少的就是平时学习的网站.以下本人收集的 Java 开发者必备的网站,这些网站可以提供信息.以及一些很棒的讲座 , 还能解答一般问题 ...
- 前端ps实用小技巧
下面总结了几个日常使用PS的小技巧,希望对大家有所帮助(重点推荐第一个小技巧) 场景一:用ps测量PSD图中的元素宽高间距时,一般是手动使用 测量,但其实是有快捷键的,如下图 首先选中元素相应图层,然 ...
- MySQL - 扩展性 2 扩展策略:氪金氪脑任君选
如果将应用的所有数据简单地放在一台 MySQL 服务器实例上,就不用谈什么扩展性了.但是业务能稳定持续的增长,那么应用肯定会碰到性能瓶颈. 对于很多类型的应用而言,购买更高性能的机器能解决一大部分性能 ...
- asp.net core系列 42 Web 应用 分部视图
一.分部视图 对于MVC 视图和 Razor Pages 页面,都有分部视图功能.通常将 MVC 视图和 Razor Pages 页面统称为“标记文件”,下面会常提到该名词.使用分部视图的优势包括:( ...
- 二分法与二叉树的 Java 实现
算法与数据结构始终是计算机基础的重要一环,今天我们来讨论下 Java 中二叉树的实现以及一些简单的小算法,如二分查找,归并排序等. 二分查找 二分查找是一种在有序数组中查找某一特定元素的搜索算法,它在 ...
- 带你由浅入深探索webpack4(一)
相信你或多或少也听说过webpack.gulp等这些前端构建工具.近年来webpack越来越火,可以说成为了前端开发者必备的工具.如果你有接触过vue或者react项目,我想你应该对它有所了解. 这几 ...
- Python调用ansible API系列(三)带有callback的执行adhoc和playbook
在第二篇文章中虽然可以执行adhoc和playbook但是执行结果的输出并不是特别直观,虽然没有报错但是到底什么结果其实你是不知道的尤其是在执行adhoc的时候,这时候我们要利用callback来设置 ...
- 微信小程序 组件通信相关知识整理
1.自定义组件间通信与事件 https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/events.htm ...