zoukankan      html  css  js  c++  java
  • 【canvas系列】canvas实现“ 简单的Amaziograph效果”--画对称图【强迫症福利】

    标题很难引人入胜,先放个效果图好了

    如果图片吸引不了你,那我觉得也就没啥看的了。

    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- 768px) {
                .wrapper {
                     auto;
                    position: relative;
                    overflow: hidden;
                }
            }
            
            @media screen and (min- 769px) {
                .wrapper {
                     600px;
                    height: 600px;
                    margin: 100px auto 0;
                    position: relative;
                    overflow: hidden;
                }
                #panel_box {
                    position: fixed;
                    top: 20px;
                    right: 20px;
                     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 {
                 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.用磨平这一侧去电容屏上画(开始吧)

    我上边那张图就是拿铅笔画的~~~

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

  • 相关阅读:
    【Qt开发】01-第一个Qt程序Hello World!
    Git使用总结(三):协同开发常见冲突
    公钥、私钥、数字签名、数字证书、对称与非对称算法、HTTPS
    通俗理解TCP的三次握手
    Flink安装极简教程-单机版
    程序员工资那些事!
    vim实战:插件安装(Vundle,NerdTree)
    Git使用总结(二):分支管理
    Git使用总结(一):简介与基本操作
    C++11并发编程:多线程std::thread
  • 原文地址:https://www.cnblogs.com/ufex/p/6689004.html
Copyright © 2011-2022 走看看