標題很難引人入勝,先放個效果圖好了

如果圖片吸引不了你,那我覺得也就沒啥看的了。
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.用磨平這一側去電容屏上畫(開始吧)
我上邊那張圖就是拿鉛筆畫的~~~
************************************
