整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

协作绘图应用程序:在画布上绘制形状

本文中,我们添加了在 HTML5 画布上选择颜色和绘制线条、圆形和正方形的功能!


绘图过程如何工作

为了在画布上绘图,我们需要知道

  • 我们用什么颜色画画
  • 我们在画什么形状
  • 无论我们是绘制形状的第一个点还是第二个点
  • 起点和终点在哪里

将这些属性添加到 Canvas 类的构造函数中:

this.activeColor = '#000000';
this.startPoint = null;
this.endPoint = null;
this.pointMode = 'start';
this.mode = 'Line';

所以我们默认画一条黑线。 现在我们添加一个函数,让我们改变我们绘制的颜色,作为 Canvas 类的一个方法:

setColor(color) {
    this.activeColor = color;
    this.ctx.strokeStyle = color;
    this.ctx.fillStyle = color;
}


使调色板工作

现在我们有了 setColor 方法,我们可以完成 Palette.draw() 方法。 我们将画布对象添加为参数,并将 onclick 事件处理程序附加到每个调色板方块:

draw(canvas) {
    const row1 = document.querySelectorAll('#row-1 .palette');
    const row2 = document.querySelectorAll('#row-2 .palette');
    row1.forEach((div, idx) => {
        div.style.backgroundColor = this.colors[0][idx];
        div.onclick = e => canvas.setColor(this.colors[0][idx]);
    });
    row2.forEach((div, idx) => {
        div.style.backgroundColor = this.colors[1][idx];
        div.onclick = e => canvas.setColor(this.colors[1][idx]);
    });
}

由于我们在这里添加了它作为参数,所以我们需要在 index.html 文件中进行设置:

palette.draw(canvas);


选择要绘制的形状

该项目允许绘制 5 种形状:线条、空心圆、实心圆、空心矩形和实心矩形。 我们默认为一行。 为了改变形状(代码中称为模式),首先将此方法添加到 Canvas :

setMode(mode) {
    this.mode = mode;
}

然后在 HTML 中,在调色板下方,添加一行按钮,让我们选择要绘制的形状:

<div id="draw-methods">
    <button onclick="canvas.setMode('Line')">Line</button>
    <button onclick="canvas.setMode('Hollow Rectangle')">Hollow Rectangle</button>
    <button onclick="canvas.setMode('Filled Rectangle')">Filled Rectangle</button>
    <button onclick="canvas.setMode('Hollow Circle')">Hollow Circle</button>
    <button onclick="canvas.setMode('Filled Circle')">Filled Circle</button>
</div>


处理要绘制的画布上的点击

这是 Canvas 的 handleDraw 方法。 我会给你代码然后解释它在做什么,因为它比我们目前看到的更复杂。

handleDraw(e) {
    const rect = this.canvas.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const y = e.clientY - rect.top;    if (this.pointMode == 'start') {
        this.startPoint = [x, y];
        this.pointMode = 'end';
    } else if (this.pointMode == 'end') {
        this.pointMode = 'start';
        this.endPoint = [x, y];
        // do the drawing
        if (this.mode == 'Line') {
            this.drawLine(this.startPoint, this.endPoint);
        } else if (this.mode == 'Hollow Rectangle') {
            this.drawHollowRectangle(this.startPoint, this.endPoint);
        } else if (this.mode == 'Filled Rectangle') {
            this.drawFilledRectangle(this.startPoint, this.endPoint);
        } else if (this.mode == 'Hollow Circle') {
            this.drawHollowCircle(this.startPoint, this.endPoint);
        } else if (this.mode == 'Filled Circle') {
            this.drawFilledCircle(this.startPoint, this.endPoint);
        }
        this.startPoint = null;
        this.endPoint = null;
    }
}

前三行是关于确定用户点击的位置。您可能会认为,当您单击画布时,鼠标单击事件只会包含您在画布中单击的位置的坐标。但这并不那么容易。你必须自己计算。

getBoundingRectClient 返回值,例如画布从左上角到左下角的距离。页面的左上角是 (0, 0),越往右越往下,数值越大。 e是传递给函数的参数,代表鼠标点击事件。 clientX 和 clientY 代表您在页面上单击的位置。减去画布元素的偏移量可以得到鼠标在画布元素中的位置。

一旦我们有了点击的位置,我们就需要知道这是第一次(“开始”)还是第二次(“结束”)点击。每个形状都是通过两次点击绘制的。对于一条线,只需选择起点和终点。对于圆,选择中心和边缘。对于矩形,选择两个对角。如果是第一次单击,则存储单击的位置,但不执行任何操作。如果是第二次单击,则存储其位置,绘制形状,然后忘记位置。

绘制形状的方法可能看起来比实际复杂。对于 drawLine ,它很简单:

drawLine(startPoint, endPoint) {
    this.ctx.beginPath();
    this.ctx.moveTo(startPoint[0], startPoint[1]);
    this.ctx.lineTo(endPoint[0], endPoint[1]);
    this.ctx.stroke();
}

绘制空心和填充的矩形只需要计算两个未点击的点:

drawHollowRectangle(startPoint, endPoint) {
    this.ctx.beginPath();
    this.ctx.strokeRect(
        startPoint[0],
        startPoint[1],
        endPoint[0] - startPoint[0],
        endPoint[1] - startPoint[1]
    );
}drawFilledRectangle(startPoint, endPoint) {
    this.ctx.beginPath();
    this.ctx.fillRect(
        startPoint[0],
        startPoint[1],
        endPoint[0] - startPoint[0],
        endPoint[1] - startPoint[1]
    );
}

绘制圆的方法需要使用距离公式计算半径,然后绘制 360 度圆弧。

drawHollowCircle(startPoint, endPoint) {
    const x = startPoint[0] - endPoint[0];
    const y = startPoint[1] - endPoint[1];
    const radius = Math.sqrt(x * x + y * y);
    this.ctx.beginPath();
    this.ctx.arc(startPoint[0], startPoint[1], radius, 0, 2 * Math.PI, false);
    this.ctx.stroke();
}drawFilledCircle(startPoint, endPoint) {
    const x = startPoint[0] - endPoint[0];
    const y = startPoint[1] - endPoint[1];
    const radius = Math.sqrt(x * x + y * y);
    this.ctx.beginPath();
    this.ctx.arc(startPoint[0], startPoint[1], radius, 0, 2 * Math.PI, false);
    this.ctx.fill();
}


附加 Canvas 事件侦听器

让画布真正响应被点击需要将这两行添加到构造函数中:

this.handleDraw = this.handleDraw.bind(this);
this.canvas.addEventListener('click', this.handleDraw);

那些使用过 React 的人可能会认识到这种模式。 但为什么需要它? 它与事件侦听器的工作方式有关。 如果没有绑定,handleDraw 中的 this 上下文变量将指向被点击的 HTML 元素。 在这种情况下,画布元素。 但这意味着我们无法访问具有我们需要的方法的 Canvas 对象。 通过使用 bind,我们强制 this 引用对象。


把它们放在一起

在此步骤中更改的文件此时应如下所示:

<html>
<head>
    <title>Collaborative Drawing App</title>
    <style type="text/css">
        canvas {
            border: 1px solid black;
        }
        .palette {
            border: 1px solid black;
            display: inline-block;
            margin: 2px;
            height: 25px;
            width: 25px;
        }
    </style>
</head>
<body>
    <h1>Collaborative Drawing App</h1>
    <div>
        <canvas id="canvas" height="500px" width="500px"></canvas><br />
    </div>
    <div id="palette">
        <div id="row-1">
            <div class="palette"></div>
            <div class="palette"></div>
            <div class="palette"></div>
            <div class="palette"></div>
            <div class="palette"></div>
            <div class="palette"></div>
            <div class="palette"></div>
            <div class="palette"></div>
            <div class="palette"></div>
            <div class="palette"></div>
        </div>
        <div id="row-2">
            <div class="palette"></div>
            <div class="palette"></div>
            <div class="palette"></div>
            <div class="palette"></div>
            <div class="palette"></div>
            <div class="palette"></div>
            <div class="palette"></div>
            <div class="palette"></div>
            <div class="palette"></div>
            <div class="palette"></div>
        </div>
    </div> 
    <div id="draw-methods">
        <button onclick="canvas.setMode('Line')">Line</button>
        <button onclick="canvas.setMode('Hollow Rectangle')">Hollow Rectangle</button>
        <button onclick="canvas.setMode('Filled Rectangle')">Filled Rectangle</button>
        <button onclick="canvas.setMode('Hollow Circle')">Hollow Circle</button>
        <button onclick="canvas.setMode('Filled Circle')">Filled Circle</button>
    </div>
    <script type="text/javascript" src="./script.js"></script>
    <script type="text/javascript">
        const canvas = new Canvas();
        const palette = new Palette();
        palette.draw(canvas);
    </script>      
</body>
</html>


class Canvas {
    constructor() {
        this.canvas = document.querySelector('#canvas');
        this.ctx = this.canvas.getContext('2d');
        this.activeColor = '#000000';
        this.startPoint = null;
        this.endPoint = null;
        this.pointMode = 'start';
        this.mode = 'Line';  
        this.handleDraw = this.handleDraw.bind(this);
        this.canvas.addEventListener('click', this.handleDraw);              
    }

    setColor(color) {
        this.activeColor = color;
        this.ctx.strokeStyle = color;
        this.ctx.fillStyle = color;
    }

    setMode(mode) {
        this.mode = mode;
    }

    handleDraw(e) {
        const rect = this.canvas.getBoundingClientRect();
        const x = e.clientX - rect.left;
        const y = e.clientY - rect.top;
        if (this.pointMode == 'start') {
            this.startPoint = [x, y];
            this.pointMode = 'end';
        } else if (this.pointMode == 'end') {
            this.pointMode = 'start';
            this.endPoint = [x, y];
            // do the drawing
            if (this.mode == 'Line') {
                this.drawLine(this.startPoint, this.endPoint);
            } else if (this.mode == 'Hollow Rectangle') {
                this.drawHollowRectangle(this.startPoint, this.endPoint);
            } else if (this.mode == 'Filled Rectangle') {
                this.drawFilledRectangle(this.startPoint, this.endPoint);
            } else if (this.mode == 'Hollow Circle') {
                this.drawHollowCircle(this.startPoint, this.endPoint);
            } else if (this.mode == 'Filled Circle') {
                this.drawFilledCircle(this.startPoint, this.endPoint);
            }
            this.startPoint = null;
            this.endPoint = null;
        }
    }
    
    drawLine(startPoint, endPoint) {
        this.ctx.beginPath();
        this.ctx.moveTo(startPoint[0], startPoint[1]);
        this.ctx.lineTo(endPoint[0], endPoint[1]);
        this.ctx.stroke();
    }

    drawHollowRectangle(startPoint, endPoint) {
        this.ctx.beginPath();
        this.ctx.strokeRect(
            startPoint[0],
            startPoint[1],
            endPoint[0] - startPoint[0],
            endPoint[1] - startPoint[1]
        );
    }

    drawFilledRectangle(startPoint, endPoint) {
        this.ctx.beginPath();
        this.ctx.fillRect(
            startPoint[0],
            startPoint[1],
            endPoint[0] - startPoint[0],
            endPoint[1] - startPoint[1]
        );
    }

    drawHollowCircle(startPoint, endPoint) {
        const x = startPoint[0] - endPoint[0];
        const y = startPoint[1] - endPoint[1];
        const radius = Math.sqrt(x * x + y * y);
        this.ctx.beginPath();
        this.ctx.arc(startPoint[0], startPoint[1], radius, 0, 2 * Math.PI, false);
        this.ctx.stroke();
    }

    drawFilledCircle(startPoint, endPoint) {
        const x = startPoint[0] - endPoint[0];
        const y = startPoint[1] - endPoint[1];
        const radius = Math.sqrt(x * x + y * y);
        this.ctx.beginPath();
        this.ctx.arc(startPoint[0], startPoint[1], radius, 0, 2 * Math.PI, false);
        this.ctx.fill();
    }    
}

class Palette {
    constructor() {
        this.colors = [
            ['#000000', '#FFFFFF', '#7F7F7F', '#C3C3C3', '#880015', '#B97A57', '#ED1C24', '#FFAEC9', '#FF7F27', '#FFC90E'],
            ['#FFF200', '#EFE4B0', '#22B14C', '#B5E61D', '#00A2E8', '#99D9EA', '#3F48CC', '#7092BE', '#A349A4', '#C8BFE7']
        ];
    }

    draw(canvas) {
        const row1 = document.querySelectorAll('#row-1 .palette');
        const row2 = document.querySelectorAll('#row-2 .palette');
        row1.forEach((div, idx) => {
            div.style.backgroundColor = this.colors[0][idx];
            div.onclick = e => canvas.setColor(this.colors[0][idx]);
        });
        row2.forEach((div, idx) => {
            div.style.backgroundColor = this.colors[1][idx];
            div.onclick = e => canvas.setColor(this.colors[1][idx]);
        });
    }
}

重新启动节点服务器后,尝试更改颜色,设置要绘制的形状,然后开始单击画布。 这是我为确保它有效而进行的一些测试:

在下一篇文章中,我们将添加一些方便的函数来显示选择的形状,以及应用程序期望用户下一步做什么的简短说明。

tml5经历了前期html快速的更新换代,以其独有特性的优势迅速占据了网页开发市场鳌头。介于目前学习和想要从事html5网页开发的人越来越多。千锋老师给大家整理了一下基本的html5学习计划路线图,适应于一些零基础学习html5的同学借鉴。

HTML5学习路线规划:

一、HTML5基础

HTML 快速入门、文本、图像、链接、表格、列表、表单、框架;

二、CSS3基础

CSS基础语法、各种选择器(通用选择器、元素选择器、id和class选择器、后代选择器、伪类选择器等)、框模型与背景、文本格式化、表格、显示与定位、浏览器调试

三、HTML5高级

HTML5 增强表单元素、HTML5验证、HTML5新事件和新属性、Canvas绘图、HTML5 SVG、音频和视频处理、离线Web存储与应用、HTML5 拖放、Web Socket API、Geolocation API、Web Worker API

四、实战技能目标

掌握JQuery核心API,HTML5 中的绘图、音频视频处理、表单新特性,轻量级WEBAPP开发。

专业的html5开发工程师需要掌握的专业技术有:

第一阶段:前端页面重构:PC端网站布局、HTML5+CSS3基础项目、WebAPP页面布局;

第二阶段:JavaScript高级程序设计:原生JavaScript交互功能开发、面向对象开发与ES5/ES6、JavaScript工具库自主研发;

第三阶段:PC端全栈项目开发:jQuery经典特效交互开发、HTTP协议,Ajxa进阶与后端开发、前端工程化与模块化应用、PC端网站开发、PC端管理信息系统前端开发;

第四阶段:移动端webAPP开发:Touch端项目、微信场景项目、应用Vue.js开发WebApp项目、应用Ionic开发WebApp项目、应用React.js开发WebApp;

第五阶段:混合(Hybrid)开发:各类混合应用开发;

第六阶段:NodeJS全栈开发:WebApp后端系统开发;

第七阶段:大数据可视化:数据可视化入门、D3.jS详解及项目实战。

HTML5开发从根本上改变了开发者开发web和应用的方式,从桌面浏览器到移动应用,HTML5都已经成为前端开发必不可少的语言。特别是移动互联网的爆发和微信这个超级应用对HTML5的支持,掌握HTML5语言的程序员已然成为各个互联网公司的标配,薪资也是一路走高。

适的动画不仅更能吸引人们的眼球,也能让你的应用体验更为流畅,而将动画的效果做到极致,才能让用户感到使用你的应用是一种享受,而不是觉得生硬和枯燥。那么Web前端人员是否了解各种前端动画效果实现方式的异同,具体应用中又是如何实现的呢?下面就让我们一起来看一看吧~



一、JavaScript 动画

因为没有其它可用的实现方式,最初的前端动画都是JS来实现,实现上就是通过一个定时器setInterval 每隔一定时间来改变元素的样式,动画结束时clearInterval即可。早期的类库包括 jquery、prototype、mootools 等等都是这种方式。

尽管这种方式动画的可控性很强,但是问题也很明显:

· 性能不佳,因为需要不断获取和修改Dom的布局,所以导致了大量页面重排(repaint)

· 缺乏标准,不同的库使用了不同的API,导致即使是简单的动画也有各不相同的实现方式,调整起来比较耗时

· 带宽消耗,相对丰富的动画库代码量都很大,结果就是增加了http请求的大小,降低了页面的载入时间

二、CSS3 动画

css3 加了两种动画的实现方式,一种是 transition, 一种是 animation。

transition 包含4种属性:transition-delay transition-duration transition-property transition-timing-function,对应动画的4种属性: 延迟、持续时间、对应css属性和缓动函数,

transform 包含7种属性:animation-name animation-duration animation-timing-function animation-delay animation-direction animation-iteration-count animation-fill-mode animation-play-state,它们可以定义动画名称,持续时间,缓动函数,动画延迟,动画方向,重复次数,填充模式。

总的来书,css 动画相比与JS更轻量,性能更好,更易于实现,同时也不必担心缺乏标准和增加带宽消耗的问题。animation 相比 transtion 使用起来更为复杂,但也提供了更多的控制,其中最重要的就是 frame 的支持,不过通过一些简单的JS库,例如 TJ 的 move.js, 我们也能在JS中通过 transition 来实现更复杂的控制。

三、Html5 动画

Html5 定义了三种绘图的方式,canvas svg Webgl,其中svg做为一种可缩放矢量图形的实现是基于xml标签定义的,它有专门的 animate 标签来定义动画。而为 canvas 或者 Webgl 实现动画则需要通过 requestAnimationFrame (简称 raf) 来定期刷新画布。尽管说 raf 的方式会让代码变得复杂,但是因为不需要那么多的文档对象(通常浏览器只需要管理一个画布),它的性能也好很多,尤其是在内存吃紧的移动端上面。

通过新的 raf 接口以及一些改进手段我们也可以用JS来实现高性能的动画。主要手段如下:

1. 减少 Dom 样式属性查询,Dom 样式属性的查询会导致页面重排,从而消耗性能,通过将属性保存在JS变量中就可以避免在动画时去查询,从而减少卡顿。

2. 使用性能更好的 css transform 替代改变绝对定位元素的定位属性

3. 在移动设备上使用 3d 硬件加速,最简单办法就是添加 -Webkit-transform: translateZ(0),原因是移动端的显卡有很强的图形渲染能力,而每个应用的 WebvieW 内存却是极其有限的。

使用JS的动画可控性更好,比如说通过事件捕捉可以很容易的设定不同参数。这方面做的最全面的有 Velocity.js,它可做为jquery 插件使用,对于初学者很友好。加入465042726,关于前端方面的更多问题我们可以一起交流!