【canvas系列】canvas實現「 簡單的Amaziograph效果」--畫對稱圖【強迫症福利】

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

若是圖片吸引不了你,那我以爲也就沒啥看的了。html

demo連接: https://win7killer.github.io/demo_set/html_demo/canvas/can_demo/draw_roll_2.html
java

*************************************************git

上次「雷達圖效果」文章很榮幸,被「某天頭條」抓數據抓去了,不開心的是demo連接等全部連接都幹掉了~~~  blabla,連個名字都木有。github

看的再看下: http://www.cnblogs.com/ufex/p/6655336.htmlcanvas

*************************************************瀏覽器

 創意來源

以前看到的gif效果,爲了這個文章又去找了一下。貌似是ipad的app 「Amaziograph」。看起來真的很爽,很美app

 

配上我本身畫的圖先:dom

 

手殘不會畫畫,各位見笑。(手機上瀏覽器畫的哦)函數

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 }

固然,這個是我嘗試屢次以後寫好的方法。

    一、存儲ctx狀態到棧,

    二、移動旋轉點(canvas座標原點)到canvas中心,

    三、旋轉指定角度,

    四、執行繪製函數fn,

    五、從棧裏邊取回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.用磨平這一側去電容屏上畫(開始吧)

我上邊那張圖就是拿鉛筆畫的~~~

************************************

相關文章
相關標籤/搜索