使用Canvas实现贝塞尔曲线显示以及控制

贝塞尔曲线

场景:

创建简易的绘图工具,实现控制画布上的四个锚点的拖拽修改已经绘制的曲线图,通过注释实时显示锚点的具体实时坐标,并通过反色清楚的显示控制点的位置

要求:
  1. 创建一个512*512的canvas画布
  2. 在该画布上任意创建四个锚点,使用贝塞尔曲线进行连接
  3. 可在画布上自由拖拽上面的锚点,从而实现贝塞尔曲线的联动,每个点都有一个跟随点移动的注释(文字+箭头),说明该点的坐标
  4.在此基础上,实现一个黑白渐变色的背景,然后在锚点周围20个像素内,将锚点所处的背景作反色,突出锚点的显示

思路整理:

明确:

贝塞尔曲线通过canvas绘制,canvas自带贝塞尔曲线绘制方法 bezierCurveTo()

显示锚点可以在canvas上覆盖div 将锚点放在div中

锚点拖动,添加事件监听, 鼠标按下 为被点击的锚点 添加鼠标移动事件,松开 注销鼠标移动事件

拖动锚点时要做什么?

修改注释内的坐标

canvas根据锚点坐标重绘贝塞尔曲线

锚点周围20像素背景色取反

效果展示

效果演示

代码(随手写 可自行优化完善)

<style>
    * {
        margin: 0;
        padding: 0;
    }

    #myCanvas {
        position: absolute;
    }

    span {
        position: absolute;
        width: 20px;
        height: 20px;
        background: #21e40f;
        border: 20px solid #fff;
        border-radius: 50%;
        color: rgb(25, 6, 204);
        text-align: center;
        font-size: 14px;
        top: 50px;
        left: 50px;
    }

    #end {
        top: 50px;
        left: 350px;
    }

    #dot1 {
        top: 350px;
        left: 50px;
    }

    #dot2 {
        top: 350px;
        left: 350px;
    }

    i {
        position: absolute;
        display: block;
        width: 60px;
        height: 40px;
        background: rgba(121, 212, 170, 0.3);
        left: 30px;
        top: 30px;
    }
</style>

<body>
    <!-- 要求:
    1. 创建一个512*512的canvas画布
    2. 在该画布上任意创建四个锚点,使用贝塞尔曲线进行连接
    3. 可在画布上自由拖拽上面的锚点,从而实现贝塞尔曲线的联动,每个点都有一个跟随点移动的注释(文字+箭头),说明该点的坐标
    4. 在此基础上,实现一个黑白渐变色的背景,然后在锚点周围20个像素内,将锚点所处的背景作反色,突出锚点的显示 -->

    <!-- 画布 -->
    <canvas id="myCanvas" width="512" height="512" style="border:1px solid #000000;">
    </canvas>
    <!-- 画布遮罩层  用于锚点显示控制 -->
    <div id="box">
        <!-- 四个锚点 开始  结束  锚点1  锚点2-->
        <span id="start">1 <i>1</i></span>
        <span id="dot1">2 <i>2</i></span>
        <span id="dot2">3 <i>3</i></span>
        <span id="end">4 <i>4</i></span>
    </div>

    <script>
        //画布渐变等
        var c, ctx, grd;
        //四个锚点
        var start, end, dot1, dot2, liArr;

        //坐标等
        var x = 0;
        var y = 0;
        var l = 0;
        var t = 0;
        var isDown = false;

        init();
        function init() {
            // 获取锚点
            start = document.querySelector('#start')
            end = document.querySelector('#end')
            dot1 = document.querySelector('#dot1')
            dot2 = document.querySelector('#dot2')
            //锚点文字说明以及跟随样式
            liArr = document.querySelectorAll('i');
            //获取画布
            c = document.getElementById("myCanvas");
            ctx = c.getContext("2d");
            // 创建渐变
            grd = ctx.createLinearGradient(0, 0, 512, 0);
            grd.addColorStop(0, "black");
            grd.addColorStop(1, "white");
            // 填充渐变
            ctx.fillStyle = grd;
            ctx.fillRect(0, 0, 512, 512);

            start.addEventListener('mousedown', downHandler)
            end.addEventListener('mousedown', downHandler)
            dot1.addEventListener('mousedown', downHandler)
            dot2.addEventListener('mousedown', downHandler)

            start.addEventListener('mouseup', upHandler)
            end.addEventListener('mouseup', upHandler)
            dot1.addEventListener('mouseup', upHandler)
            dot2.addEventListener('mouseup', upHandler)

            ctx.beginPath();
            drawLine()
            ctx.stroke();
        }
        //鼠标按下回调
        function downHandler(e) {
            e.stopPropagation();
            // 获取x坐标和y坐标
            x = e.clientX;
            y = e.clientY;
            //获取左部和顶部的偏移量
            l = e.target.offsetLeft;
            t = e.target.offsetTop;
            //开关打开
            isDown = true;
            //设置样式
            e.target.style.cursor = 'move';
            //添加移动侦听
            e.target.addEventListener('mousemove', moveHandler)
            e.target.addEventListener('mouseup', moveHandler)
        }
        //拖拽回调
        function moveHandler(e) {
            if (isDown == false) {
                return;
            }
            //获取x和y
            var nx = e.clientX;
            var ny = e.clientY;
            //计算移动后的左偏移量和顶部的偏移量
            var nl = nx - (x - l);
            var nt = ny - (y - t);

            e.target.style.left = nl + 'px';
            e.target.style.top = nt + 'px';

            ctx.beginPath();
            drawLine();
            ctx.stroke();
        }

        function upHandler(e) {
            //注销移动侦听事件
            e.target.removeEventListener('mousemove', moveHandler)
        }
        //绘制贝塞尔曲线 /  设置 文字  /  背景反色
        function drawLine() {
            //获取锚点坐标
            let s1 = setNum(start.offsetLeft);
            let s2 = setNum(start.offsetTop);
            let e1 = setNum(end.offsetLeft);
            let e2 = setNum(end.offsetTop);
            let dd1 = setNum(dot1.offsetLeft);
            let dd2 = setNum(dot1.offsetTop);
            let dd3 = setNum(dot2.offsetLeft);
            let dd4 = setNum(dot2.offsetTop);

            ctx.clearRect(20, 20, 100, 50);
            ctx.fillStyle = grd;
            ctx.fillRect(0, 0, 512, 512);

            ctx.moveTo(s1, s2)
            ctx.bezierCurveTo(dd1, dd2, dd3, dd4, e1, e2);
            //显示更新锚点坐标
            liArr[0].innerText = `x=> ${s1} x=> ${s2}`;
            liArr[1].innerText = `x=> ${dd1} x=> ${dd2}`;
            liArr[2].innerText = `x=> ${dd3} x=> ${dd4}`
            liArr[3].innerText = `x=> ${e1} x=> ${e2}`

            //设置边框颜色
            setColor(start, s1, s2);
            setColor(end, e1, e2);
            setColor(dot1, dd1, dd2);
            setColor(dot2, dd3, dd4);
        }
        //处理贝塞尔曲线坐标点, 与div 中心重合
        function setNum(str) {
            return parseInt(str) + 30
        }
        //设置背景色反色
        function setColor(target, x, y) {
            let col = ctx.getImageData(x, y, 1, 1).data;
            for (let i = 0; i < col.length; i++) {
                col[i] = 255 - col[i]
            }
            console.log(target, col);
            target.style.border = `20px solid rgb(${col[0]},${col[1]},${col[2]})`
        }
    </script>
</body>

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,099评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,828评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,540评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,848评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,971评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,132评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,193评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,934评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,376评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,687评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,846评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,537评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,175评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,887评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,134评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,674评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,741评论 2 351

推荐阅读更多精彩内容