标题很难引人入胜,先放个效果图好了
如果图片吸引不了你,那我觉得也就没啥看的了。
demo链接: https://win7killer.github.io/demo_set/html_demo/canvas/can_demo/draw_roll_2.html
*************************************************
上次“雷达图效果”文章很荣幸,被“某天头条”抓数据抓去了,不开心的是demo链接等所有链接都干掉了~~~ blabla,连个名字都木有。
想看的再看下: http://www.cnblogs.com/ufex/p/6655336.html
*************************************************
创意来源
之前看到的gif效果,为了这个文章又去找了一下。貌似是ipad的app “Amaziograph”。看起来真的很爽,很美
配上我自己画的图先:
手残不会画画,各位见笑。(手机上浏览器画的哦)
DEMO讲解
1.效果分析
a.参考线坐标轴 -- 为了简单控制参考线显示隐藏,单独一个canvas来搞,也不用每次重绘
b.绘画主体 -- 绘画效果(canvas画线);对称效果(canvas旋转)
c.配置区 -- 简单dom
简单来看,很容易实现嘛
2.开搞
1> 坐标系统
其实就是画几条线,但是要均分角度。一种方法是,计算出各个点,然后从中心点发散去画线;另一种是,一边旋转canvas,一边画圆心到统一坐标的线。由于绘画是需用到canvas旋转,所以这里统一使用旋转来处理。
那么,就需要先来处理canvas旋转
1 function drawRotate(deg, fn, _ctx) {
2 _ctx = _ctx || ctx
3 _ctx.save();
4 _ctx.translate(_ctx.canvas.width / 2, _ctx.canvas.height / 2);
5 _ctx.rotate(deg);
6 fn && fn(_ctx);
7 _ctx.restore();
8 }
当然,这个是我尝试多次之后写好的方法。
1、存储ctx状态到栈,
2、移动旋转点(canvas坐标原点)到canvas中心,
3、旋转指定角度,
4、执行绘制函数fn,
5、从栈里边取回ctx的状态(包含但不仅包含 fillStyle、strokenStyle、translate等等),这里主要处理的是translate,因为我们下次用到坐标会受影响,所以要让canva坐标原点回到原来的位置。
其实这里translate还是比较抽象比较绕的。。。可能我比较迟缓
然后,是绘制参考线坐标
1 function baseLine() {
2 ctx_role.clearRect(0, 0, ctx_role.canvas.width, ctx_role.canvas.height);
3 var deg = 360 / pieace;
4 console.log(deg);
5 ctx_role.lineWidth = 1;
6 ctx_role.strokeStyle = 'rgba(0,0,0,.5)';
7 for (var i = 0, l = pieace; i < l; i ) {
8 drawRotate(i * deg / 180 * Math.PI, function(ctx_role) {
9 draw({
10 bx: can_role.width / 2,
11 by: can_role.width / 2,
12 ex: can_role.width / 2 can_role.width,
13 ey: can_role.width / 2
14 }, ctx_role);
15 }, ctx_role);
16 }
17 }
1 function draw(option, _ctx) {
2 _ctx = _ctx || ctx;
3 _ctx.beginPath();
4 _ctx.moveTo(option.bx - _ctx.canvas.width / 2, option.by - _ctx.canvas.height / 2);
5 _ctx.lineTo(option.ex - _ctx.canvas.width / 2, option.ey - _ctx.canvas.height / 2);
6 _ctx.stroke();
7 }
这样,就绘制完成参考线。
2>绘画主体
首先处理一般的画线。跟拖拽效果类似,在move过冲中一直画线链接两个点。对拖拽不了解的可以去了解下,直接上代码
1 function bindPc() {
2 can.onmousedown = function(e) {
3 if (e.button != 0) {
4 return false;
5 }
6
7 var op = {};
8 op.ex = op.bx = e.clientX - can.parentElement.offsetLeft window.scrollX;
9 op.ey = op.by = e.clientY - can.parentElement.offsetTop window.scrollY;
10 drawFn(op);
11 document.onmousemove = function(e) {
12 document.body.style.cursor = 'pointer';
13 op.bx = op.ex;
14 op.by = op.ey;
15 op.ex = e.clientX - can.parentElement.offsetLeft window.scrollX;
16 op.ey = e.clientY - can.parentElement.offsetTop window.scrollY;
17 drawFn(op);
18 };
19 document.onmouseup = function() {
20 document.body.style.cursor = 'default';
21 document.onmouseup = document.onmousemove = null;
22 };
23 };
24 }
1 function drawFn(op) {
2 var deg = Math.floor(360 / pieace);
3 for (var i = 0, l = 360; i < l; i = deg) {
4 drawRotate(i / 180 * Math.PI, function(ctx) {
5 draw(op);
6 });
7 }
8 }
需要注意,e.button 用来判断是鼠标哪个键,0是左键
这里又用到了前边的drawRotate 和 draw。
************************************
至此,应该可以画出对称的线条了。
以下就是锦上添花的事情了
************************************
增加移动端的绘制支持(惭愧,没怎么写过移动端,欢迎多指教)
1 function bindWp() {
2 can.addEventListener('touchstart', function(e) {
3 op = can.op = {};
4 op.ex = op.bx = e.touches[0].clientX - can.parentElement.offsetLeft window.scrollX;
5 op.ey = op.by = e.touches[0].clientY - can.parentElement.offsetTop window.scrollY;
6 drawFn(op);
7 can.addEventListener('touchmove', touchMoveFn);
8 can.addEventListener('touchend', touchEndFn);
9 });
10
11 function touchEndFn() {
12 document.body.style.cursor = 'default';
13 can.removeEventListener('touchmove', touchMoveFn);
14 can.removeEventListener('touchend', touchEndFn);
15 }
16
17 function touchMoveFn(e) {
18 op = can.op;
19 document.body.style.cursor = 'pointer';
20 op.bx = op.ex;
21 op.by = op.ey;
22 op.ex = e.touches[0].clientX - can.parentElement.offsetLeft window.scrollX;
23 op.ey = e.touches[0].clientY - can.parentElement.offsetTop window.scrollY;
24 drawFn(op);
25 return false;
26 }
27 }
3>设置等
这里dom比较简单,就略过了。只说一项,下载canvas图片到本地
最简单的,右键保存图片到本地,但是你肯定会骂我傻,谁不知道这操作啊;那么就来稍微装X一下吧
线上代码
1 function download() {
2 var data = can.toDataURL('image/png', 0.8);
3 var $a = document.createElement('a');
4 $a.download = imgName.value || 'default.png';
5 $a.target = '_blank';
6 $a.href = data;
7 $a.click();
8 }
(写这个博客的时候,返现自己把这个方法写麻烦了,绕远了。/手动尴尬,这里直接改了)
关键点在于 a.download属性,这个是把文件下载到本地的关键哦,然后要把canvas转成base64(canvas.toDataUrl方法,不清楚的可以去去了解下,这里不再赘述)
******************************************************
最后,附上完整代码(可能会和上边的有点出如,还在调整)
<!DOCTYPE html>
<html lang="zh"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><mtea author="win7killer@163.com"></mtea><title>Document</title><style>* {margin: 0;padding: 0;}p {line-height: 15px;font-size: 12px;}@media screen and (max-width: 768px) {.wrapper {width: auto;position: relative;overflow: hidden;}}@media screen and (min-width: 769px) {.wrapper {width: 600px;height: 600px;margin: 100px auto 0;position: relative;overflow: hidden;}#panel_box {position: fixed;top: 20px;right: 20px;width: 200px;}}canvas {background: #fafafa;display: block;}#can_role {background: none;position: absolute;top: 0px;left: 0px;pointer-events: none;}#panel_box {padding: 10px;margin-top: 10px;border: 1px solid rgba(10, 10, 10, .7);box-shadow: 10px 7px 10px #999;z-index: 100;}input {width: 80px;margin-left: 20px;}label {text-align: justify;}</style>
</head><body><div class="wrapper" id="wrapper"><canvas id="can_role"></canvas><canvas id="can"></canvas></div><div id="panel_box"><p><label>画笔颜色<input id="color_val" type="color" value="#0099ff"/></label></p><p><label>画笔宽度<input type="number" id="line_width_val" min="1" max="20" value="2"/></label></p><p><label>扇形份数<input type="number" id="pieaceNum" min="1" max="200" value="12"/></label></p><p><label>参考线<input type="checkbox" id="onOff" checked="checked"/></label></p><p class="img_name_box"><label>图片名称<input type="text" id="imgName" placeholder="ex:test.png"></label></p><p><a href="javascript:;" id="save_btn" target="">下载到本地</a></p></div><script>var pieace = 6;var ctx = can.getContext('2d');var ctx_role = can_role.getContext('2d');can.width = can.height = can_role.width = can_role.height = window.screen.width > 768 ? 600 : window.screen.width;ctx_role.lineJoin = ctx.lineJoin = "round";ctx_role.lineCap = ctx.lineCap = "round";function drawFn(op) {var deg = Math.floor(360 / pieace);for (var i = 0, l = 360; i < l; i = deg) {drawRotate(i / 180 * Math.PI, function(ctx) {draw(op);});}}function draw(option, _ctx) {_ctx = _ctx || ctx;_ctx.beginPath();_ctx.moveTo(option.bx - _ctx.canvas.width / 2, option.by - _ctx.canvas.height / 2);_ctx.lineTo(option.ex - _ctx.canvas.width / 2, option.ey - _ctx.canvas.height / 2);_ctx.stroke();}function drawRotate(deg, fn, _ctx) {_ctx = _ctx || ctx_ctx.save();_ctx.translate(_ctx.canvas.width / 2, _ctx.canvas.height / 2);_ctx.rotate(deg);fn && fn(_ctx);_ctx.restore();}function baseLine() {ctx_role.clearRect(0, 0, ctx_role.canvas.width, ctx_role.canvas.height);var deg = 360 / pieace;ctx_role.lineWidth = 1;ctx_role.strokeStyle = 'rgba(0,0,0,.5)';for (var i = 0, l = pieace; i < l; i ) {drawRotate(i * deg / 180 * Math.PI, function(ctx_role) {draw({bx: can_role.width / 2,by: can_role.width / 2,ex: can_role.width / 2 can_role.width,ey: can_role.width / 2}, ctx_role);}, ctx_role);}}function download() {var data = can.toDataURL('image/png', 0.8);var $a = document.createElement('a');$a.download = imgName.value || 'default.png';$a.target = '_blank';$a.href = data;$a.click();// if (typeof MouseEvent === 'function') {// var evt = new MouseEvent('click', {// view: window,// bubbles: true,// cancelable: false// });// $a.dispatchEvent(evt);// }}function bindPc() {can.onmousedown = function(e) {if (e.button != 0) {return false;}var op = {};op.ex = op.bx = e.clientX - can.parentElement.offsetLeft window.scrollX;op.ey = op.by = e.clientY - can.parentElement.offsetTop window.scrollY;drawFn(op);document.onmousemove = function(e) {document.body.style.cursor = 'pointer';op.bx = op.ex;op.by = op.ey;op.ex = e.clientX - can.parentElement.offsetLeft window.scrollX;op.ey = e.clientY - can.parentElement.offsetTop window.scrollY;drawFn(op);};document.onmouseup = function() {document.body.style.cursor = 'default';document.onmouseup = document.onmousemove = null;};};}function bindWp() {can.addEventListener('touchstart', function(e) {op = can.op = {};op.ex = op.bx = e.touches[0].clientX - can.parentElement.offsetLeft window.scrollX;op.ey = op.by = e.touches[0].clientY - can.parentElement.offsetTop window.scrollY;drawFn(op);can.addEventListener('touchmove', touchMoveFn);can.addEventListener('touchend', touchEndFn);});function touchEndFn() {document.body.style.cursor = 'default';can.removeEventListener('touchmove', touchMoveFn);can.removeEventListener('touchend', touchEndFn);}function touchMoveFn(e) {op = can.op;document.body.style.cursor = 'pointer';op.bx = op.ex;op.by = op.ey;op.ex = e.touches[0].clientX - can.parentElement.offsetLeft window.scrollX;op.ey = e.touches[0].clientY - can.parentElement.offsetTop window.scrollY;drawFn(op);return false;}}function bindSets() {color_val.onchange = function() {ctx.strokeStyle = color_val.value;}line_width_val.onchange = function() {ctx.lineWidth = line_width_val.value;}pieaceNum.onchange = function() {ctx.clearRect(0, 0, can.width, can.height);reset();}onOff.onchange = function() {if (this.checked == true) {can_role.style.display = 'block';} else {can_role.style.display = 'none';}}}function bind() {bindPc();bindWp();bindSets();save_btn.onclick = download;}function reset() {pieace = pieaceNum.value;ctx.strokeStyle = 'rgba(100,100,100,.7)';baseLine();ctx.lineWidth = line_width_val.value;ctx.strokeStyle = color_val.value;}function init() {reset();bind();}init();</script>
</body></html>
**************偷偷留个名字,防抓 博客园-fe-bean***************
涉及姿势点总结
1.canvas_translate
2.canvas_rotate
3.canvas_toDataUrl
4.a.download && base64
其余的想起来再添加吧
最后,欢迎大家多提意见、交流,点赞转载那就更棒了。
再丢一张图
下期再见咯~~~
**************** 少侠留步,能看到这里的,我要给你们一个奖励 ***************
这个demo是可以在移动端玩的,意味着有电容笔的亲,可以爽啊~(个别浏览器脑残会左右来回跑~~)
没有电容笔的亲,肯定是大多数,我们一样能玩啊!!!
叫你们快速做一款电容笔(当然没那么好用)
1.找一只木质铅笔
2.削出铅笔头
3.把铅笔头斜着磨平,如图
4.用磨平这一侧去电容屏上画(开始吧)
我上边那张图就是拿铅笔画的~~~
************************************
更多专业前端知识,请上 【猿2048】www.mk2048.com