前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >利用canvas实现毛笔字帖(一)

利用canvas实现毛笔字帖(一)

作者头像
用户1394570
发布于 2018-08-02 03:26:45
发布于 2018-08-02 03:26:45
2.8K00
代码可运行
举报
文章被收录于专栏:carvencarven
运行总次数:0
代码可运行

最近在慕课网上找到了很好的canvas教程, 来自 @liuyubobobo学写一个字 canvas绘图教程 在 @liuyubobobo 老师的系列canvas教程中,我学到不少知识。 今天,运用在视频中的所学,结合自己的代码风格,我自己尝试也写一个字帖出来,在这里分享一下思路和过程 具体代码其实已经push在github上,感兴趣的可以clone下来参考一下。 代码/canvas-demo/write 这里还有在线的效果演示 在线演示

想法

在canvas在实现这样一个效果,有一个米字格,可以用鼠标(pc)甚至手指(手机)在上面写字,字要有点像毛笔字。 下面有控制部件,可以控制笔的颜色,还是可以清空米字格的墨迹。

下面就开始着手去写了。

代码

css和html部分不是很多,也不是我要讲的重点,简单贴出来

html

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>毛笔字</title>
    <meta name="viewport" content="width=device-width;height=device-height;initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <link href="css/write.css" rel="stylesheet" type="text/css">
</head>
<body>
<canvas id="canvas">
    您的浏览器版本不支持canvas,请更新或者下载chrome
</canvas>
<div id="controller">
    <div id="black" class="on"></div>
    <div id="red"></div>
    <div id="green"></div>
    <div id="yellow"></div>
    <div id="purple"></div>
    <div id="orange"></div>
    <div id="blue"></div>
    <div id="indigo"></div>
    <button id="reset">清除</button>
</div>
</body>
</html>

css

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#canvas{
    display: block;  margin: 20px auto;  border: #ff1722 5px solid;
}
/*控制器*/
#controller{
    text-align: center;
}
#controller>div{
    display: inline-block;  width: 30px;  height: 30px;  border: gray 2px solid;
}
#black{
    background: black;
}
#red{
    background: red;
}
#green{
    background: green;
}
#yellow{
    background: yellow;
}
#purple{
    background: purple;
}
#orange{
    background: orange;
}
#blue{
    background: blue;
}
#indigo{
    background: indigo;
}
#controller>div.on{
    border-color: red;
}
#reset{
    display: inline-block;  vertical-align: top;  width: 50px;  height: 34px;
}

javascript

js部分就很关键了,涉及到很多api,交互流程,编程思路,下面慢慢来讲:


一、 按功能设计代码模块

首先,按照功能分类,我们将代码分成3个模块

1. 第1部分paper.js 负责绘制出米字格的字帖背景 代码设计属性如下 paper.js

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var paper = {
        canvas: null,//html中的canvas对象,主要标签
        context: null, //canvas对象获取的context,用于绘图
        init: function (canvas) {
        //初始化方法
        },
        drawPaper: function(){
        //绘制米字格图纸的接口
        },

        drawDotted: function(sx, sy, ex, ey){
        //绘制虚线的接口
        }
    };

2. 第2部分controller.js 负责控制画笔的颜色和清理画布,即控制面板的功能实现。 代码设计属性如下 controller.js

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var controller = {
        canvas: null,//html中的canvas对象,主要标签
        context: null, //canvas对象获取的context,用于绘图
        init: function (canvas) {
        //初始化方法
        },
        bindEvent: function () {
        //事件监听的设置区域
        },

        setColor: function (target) {
        //设置画笔颜色的接口
        },

        clear: function () {
        //清理画布墨迹的接口
        }
    };

3. 第3部分write.js 第三部分也是最关键最复杂的部分,负责描绘出鼠标(手指)划过的笔画 因为要协调笔画,会用到比较多的辅助函数和辅助参数,下面会一一介绍 write.js

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var write = {
        canvas: null, //html中的canvas对象,主要标签
        context: null, //canvas对象获取的context,用于绘图
        isWriting: false,//是否正在下笔写字
        lineWidthMax: 0, //画笔最大粗细
        lineWidthMin: 1, //画笔最小粗细

        lastX: 0,//画笔上次停留位置
        lastY: 0,
        lastTime: 0, //上次移笔时间
        lastLineWidth: 0,//上次画下的笔宽
        init: function (canvas) {
        //初始化方法
        },
        bindEvent: function () {
        //事件监听的设置区域
        },

        //描绘区
        startWrite: function (co) {
        //开始落笔
        },
        writing: function (co) {
        //笔移动,正在写字
        },
        endWrite: function(){
        //收笔
        },

        //辅助函数区
        getCo: function (clientX, clientY) {
        //返回落笔的位置
        },
        getS: function (sx, sy, ex, ey) {
        //返回两点间的距离
        },
        getLineWidth: function (s, t) {
        //计算笔迹的线条宽度
        }
    };
二、 根据功能需要完善代码

1. 第1部分paper.js 针对paper模块,我们知道,是用来设置字帖纸的样式的。 我们要做的有两步

  • 一、 根据屏幕宽度设置米字格大小,兼容pc和手机
  • 二、 用虚线绘制出米字 (边框线条已经用css实现了)

初始化方法init()是一个模块最开始的地方。我们从 init开始,边看代码边解释 外接通过编写如下代码调用paper,完成其初始化,并让运行

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var canvas = document.getElementById('cnavas');
paper.init(canvas);

所以我们在paper.init中接受外界传来的canvas,并利用它完成初始化,运行绘制方法,编写如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var paper = {
        canvas: null,//html中的canvas对象,主要标签
        context: null, //canvas对象获取的context,用于绘图
        init: function (canvas) {
            this.canvas = canvas;//接收外界canvas,赋值给自己的属性``canvas``,在下面的其他方法中需要用到
            this.context = canvas.getContext('2d');//通过canvas获取context,赋值给自己的属性``context``,在下面的其他方法中需要用到

            //动态设置canvas大小,兼容手机和pc
            this.canvas.width = Math.min(500, window.innerWidth - 20);// 米字格最大只能为500px
            this.canvas.height = this.canvas.width;

            this.drawPaper(); //绘制米字格背景,自己完善drawPaper方法
        },
        drawPaper: function() {},
        drawDotted: function(sx, sy, ex, ey){}
        //......
    };

drawPaper()方法就更加简单了,只需要调用drawDotted()接口绘制线段就可以了

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//...
        drawPaper: function(){
            this.drawDotted(0, 0, this.canvas.width, this.canvas.height);//左上角到右下角的斜线
            this.drawDotted(this.canvas.width, 0, 0, this.canvas.height);//左下角到右上角的斜线
            this.drawDotted(this.canvas.width/2, 0, this.canvas.width/2, this.canvas.height);//中间的横线
            this.drawDotted(0, this.canvas.height/2, this.canvas.width, this.canvas.height/2);//中间的竖线
        },
        drawDotted: function(sx, sy, ex, ey){}
//...

drawDotted() 实现起来需要自己构思了,最新的h5标准已经有setLineDash用来绘制虚线,ie11以上都行,支持性不错,但是为了兼容,我们也尝试手写一些虚线函数出来

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//···
        drawDotted: function(sx, sy, ex, ey){
            var lineInterval = 5;//虚线的间隔
            this.context.save();//保存当前的context状态(快照,用于恢复,防止状态设置紊乱污染)

            this.context.lineWidth = 3;//线宽
            this.context.strokeStyle = '#ff1722';//线条颜色

            //setLineDash 虚线设置接口比较新,为了保险起见,自己编写一下
            if(this.context.setLineDash){ // 使用h5的setLineDash方法
                this.context.setLineDash([lineInterval, lineInterval]);//[线宽, 间隔宽]
                this.context.moveTo(sx,sy);
                this.context.lineTo(ex,ey);
            }
            else{//setLineDash不存在,自己手动处理
                //len 虚线要绘制成多少段
                var len = Math.ceil(Math.sqrt((ex - sx)*(ex - sx) + (ey - sy)*(ey - sy)) / lineInterval /2);
                var lineIntervalX = (ex - sx) / len;//每一段间x轴上的间隔
                var lineIntervalY = (ey - sy) / len;//每一段间y轴上的间隔
                var index = 0;//计数器
                this.context.beginPath();
                while (index < len) {//开始定制路线
                    var targetX = sx + lineIntervalX;//计算当前段绘制的终点
                    var targetY = sy + lineIntervalY;
                    this.context.moveTo(sx, sy);//起点
                    this.context.lineTo(targetX, targetY);//终点

                    sx = targetX + lineIntervalX;//计算下一段绘制的起点
                    sy = targetY + lineIntervalY;

                    index ++;
                }
            }

            this.context.stroke();//绘制线条
            this.context.restore();//恢复保存的状态,对应 save() 方法
        }
//···

如此,一个米字格图纸的绘制方法已经完成了,下面的代码汇总

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var paper = {
        canvas: null,//html中的canvas对象,主要标签
        context: null, //canvas对象获取的context,用于绘图
        init: function (canvas) {
            this.canvas = canvas;//接收外界canvas,赋值给自己的属性``canvas``,在下面的其他方法中需要用到
            this.context = canvas.getContext('2d');//通过canvas获取context,赋值给自己的属性``context``,在下面的其他方法中需要用到

            //动态设置canvas大小,兼容手机和pc
            this.canvas.width = Math.min(500, window.innerWidth - 20);// 米字格最大只能为500px
            this.canvas.height = this.canvas.width;

            this.drawPaper(); //绘制米字格背景,自己完善drawPaper方法
        },
        drawPaper: function(){
            this.drawDotted(0, 0, this.canvas.width, this.canvas.height);//左上角到右下角的斜线
            this.drawDotted(this.canvas.width, 0, 0, this.canvas.height);//左下角到右上角的斜线
            this.drawDotted(this.canvas.width/2, 0, this.canvas.width/2, this.canvas.height);//中间的横线
            this.drawDotted(0, this.canvas.height/2, this.canvas.width, this.canvas.height/2);//中间的竖线
        },

        drawDotted: function(sx, sy, ex, ey){
            var lineInterval = 5;//虚线的间隔
            this.context.save();//保存当前的context状态(快照,用于恢复,防止状态设置紊乱污染)

            this.context.lineWidth = 3;//线宽
            this.context.strokeStyle = '#ff1722';//线条颜色

            //setLineDash 虚线设置接口比较新,为了保险起见,自己编写一下
            if(this.context.setLineDash){ // 使用h5的setLineDash方法
                this.context.setLineDash([lineInterval, lineInterval]);//[线宽, 间隔宽]
                this.context.moveTo(sx,sy);
                this.context.lineTo(ex,ey);
            }
            else{//setLineDash不存在,自己手动处理
                //len 虚线要绘制成多少段
                var len = Math.ceil(Math.sqrt((ex - sx)*(ex - sx) + (ey - sy)*(ey - sy)) / lineInterval /2);
                var lineIntervalX = (ex - sx) / len;//每一段间x轴上的间隔
                var lineIntervalY = (ey - sy) / len;//每一段间y轴上的间隔
                var index = 0;//计数器
                this.context.beginPath();
                while (index < len) {//开始定制路线
                    var targetX = sx + lineIntervalX;//计算当前段绘制的终点
                    var targetY = sy + lineIntervalY;
                    this.context.moveTo(sx, sy);//起点
                    this.context.lineTo(targetX, targetY);//终点

                    sx = targetX + lineIntervalX;//计算下一段绘制的起点
                    sy = targetY + lineIntervalY;

                    index ++;
                }
            }

            this.context.stroke();//绘制线条
            this.context.restore();//恢复保存的状态,对应 save() 方法
        }
    };

如此,在html中调用 paper.init(canvas), 一个米字格就会成功绘制在你的面前,是不是很简单又有趣

边幅有点长,这是绘制的第一部分,我们在接下来的一篇博客里再讲第二部分,请期待 利用canvas实现毛笔字帖(二), 跟大家真正实现毛笔写字的部分

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
SONION - MEMS Mic骨传导拾音解决方案
华为于IFA柏林新发布的FreeBuds3 TWS耳机采用了SONION的MEMS Mic解决方案。通过骨震动(Bone Vibriation),从可能的环境噪音中精准检测和识别语音。
用户6026865
2019/09/10
6.6K0
SONION - MEMS Mic骨传导拾音解决方案
超低功耗解决方案如何赋能Always-on语音交互系统
消费者越来越需要可以随时通过语音控制的产品,可以与数字世界更加安全的和自然的交互。
用户6026865
2020/07/06
1.6K0
超低功耗解决方案如何赋能Always-on语音交互系统
10张高清大图!读懂十大国产MEMS厂商技术路线
在3月初举办的2023年中国MEMS制造大会(China MEMS 2023)上,公布了“2021中国MEMS十强企业”名单。该名单由中国半导体行业协会根据行业季度统计报表及各地方协会统计数据评选出。
用户10432078
2023/03/21
1.3K1
10张高清大图!读懂十大国产MEMS厂商技术路线
可编程USB Type-C 全向麦克风
首先实现起来够简单,传感器+MCU是我非常推崇的产品设计方式,还有就是USB连接,可以和算力强的机器互动。如果换ESP32-S3,可以直接运行一些轻量的神经网络算法。
云深无际
2024/10/21
1420
可编程USB Type-C 全向麦克风
智能语音交互中的麦克风阵列技术
近年来,随着语音识别技术的发展成熟,语音交互越来越多的走进我们的生活。从苹果手机Siri助手的横空出世开始,各大公司纷纷效仿开发自己的语音助手和语音识别平台,手机端的近场语音交互日趋成熟。后来Amazon发布Echo智能音箱,开启了智能硬件远场语音交互时代。相比于Siri手机端近场的语音交互,Echo音箱的语音交互支持距离更远,交互更加自然便捷,它使用了麦克风阵列来保证远距离复杂背景噪声和干扰环境下的良好拾音效果,随后麦克风阵列逐渐成为了后续语音交互智能硬件的标配。
DancingWind
2019/08/01
11.2K0
智能语音交互中的麦克风阵列技术
浅析硬件“好声音”:麦克风技术指标及选型指南
这类设备中都集成了麦克风和喇叭等电声器件,其中麦克风用于识别用户的声音,喇叭用于播放设备对用户指令的反应。麦克风的性能是影响语音唤醒率高低的重要因数,而喇叭的性能会影响打断唤醒率和用户的主观体验。接下来将分两篇文章对麦克风和喇叭的一些主要性能参数进行解析,给大家在产品设计时选择声学器件提供一些帮助。
硬件大熊
2022/06/23
3.1K0
浅析硬件“好声音”:麦克风技术指标及选型指南
令人激动的语音UI背后
亚马逊Echo和Echo Dot智能音箱获得了成功,它已经使语音命令(通常称为语音UI或语音UI)出现在了新技术产品中。在每一部智能手机和平板电脑上,大多数新型汽车上,以及快速增长的音频产品中,都有这个功能。最终,大多数家用电器,音频和视频产品,甚至像健身跟踪器这样的可穿戴设备,最终也都会有语音命令功能。
半吊子全栈工匠
2018/08/22
1.6K0
令人激动的语音UI背后
科大讯飞李伟:人机交互如何选择合适的「耳朵」
AI 研习社按:人工智能当前正处于爆发阶段,语音交互作为人工智能的重要组成部分正在各行业全面的落地,在人机进行语音交互的过程中,机器需要通过耳朵实现听觉的作用。
AI研习社
2018/07/26
1.3K0
科大讯飞李伟:人机交互如何选择合适的「耳朵」
Knowles的AISonic™ 白色家电标准解决方案
白色家电设备的语音集成机会在多个方面都面临挑战。楼氏 标准解决方案包括一套强大的音频前端算法,使消费者的声音能够在嘈杂的厨房或杂物间中被听到。楼氏 DSP还可以运行一个边缘ASR,用于本地命令和控制,并减少网络连接延迟。该套件是建立在楼氏 行业领先的AISonic™ 音频边缘处理器IA8201因其远场语音处理的性能而受到认可。 AISonic™ 白色家电标准解决方案套件平衡了可扩展性和灵活性,使其易于集成到各种嵌入式平台。该套件有两种选择,一种是与入门级电器中的嵌入式MCU一起使用,另一种是与带有触摸HUI的AP一起使用。IA8201参考板配有一个软件包,其中包括一个支持与AP上运行RTOS或Android的嵌入式MCU集成的API。该软件包还包括固件发布,以支持任何传感器和来自楼氏 的预集成麦克风,实现最高质量的音频捕获。
用户6026865
2021/07/08
7100
Knowles的AISonic™ 白色家电标准解决方案
CapSpeaker:基于电容器的麦克风声音注入攻击
语音助手可以被各种恶意语音命令操纵,但现有的攻击需要附近的扬声器来播放攻击命令。在本文中展示了即使在没有扬声器可用的情况下,也可以利用电子设备内部的电容器来播放恶意命令,即将电容器转换为扬声器,并将其称为 CapSpeaker。本质上,由于逆压电效应,电容器会发出声学噪声,即改变电容器两端的电压会使其振动,从而发出声学噪声。强制电容器播放恶意语音命令具有挑战性,因为:
CDra90n
2023/03/13
1.4K0
CapSpeaker:基于电容器的麦克风声音注入攻击
赛莱克斯深耕MEMS细分领域创新研发构建核心技术壁垒
赛莱克斯微系统科技(北京)有限公司(以下简称“赛莱克斯北京”)成立于2015年12月15日,注册资本20亿元, 由北京赛微电子股份有限公司和国家集成电路产业投资基金共同投资,主要从事MEMS的生产代工业务,是国家高新技术企业、北京市专精特新中小企业。赛莱克斯北京集技术开发、技术转让、技术咨询、技术服务及产品制造于一体,生产MEMS传感器等产品,应用于物联网、可穿戴设备、消费电子、工业设备等多领域。赛莱克斯北京已获授权的发明专利7项,正在申请中的发明专利70项。
云上计算
2023/02/24
3620
赛莱克斯深耕MEMS细分领域创新研发构建核心技术壁垒
麦克风阵列声源定位程序_麦克风阵列怎么设置
利用麦克风阵列可以实现声源到达方向估计(direction-of-arrival (DOA) estimation),DOA估计的其中一种方法是计算到达不同阵元间的时间差,另外一种可以看这里,这篇主要介绍经典的GCC-PHAT方法
全栈程序员站长
2022/11/09
1.9K0
麦克风阵列声源定位程序_麦克风阵列怎么设置
浅析硬件“好声音”: 声学器件
随着智能化产品的普及,音视频硬件在电子设计中所占的比例越来越高。常见如电视机、导航仪、商超机器人等。音视频硬件中,声学器件是必不可少的基础元件,声学器件主要包括麦克风和喇叭,麦克风拾取声音,喇叭播放声音。麦克风和喇叭的性能优劣,会直接影响到智能语音设备的人机交互体验,本文将对麦克风和喇叭的基础知识做一个简单介绍。
硬件大熊
2022/06/23
9880
浅析硬件“好声音”: 声学器件
iOS开发之──传感器使用 (转载)
在实际的应用开发中,会用到传感器,下面首先介绍一下iphone4的传感器,然后对一些传感器的开发的API作一简单介绍。
tandaxia
2018/09/27
9250
TWS耳机的开挂人生
2016年,苹果推出的 AirPods 以主动降噪+真无线TWS耳机,引爆市场。TWS 耳机正式开启开挂人生。
AI 电堂
2021/02/07
1.7K0
简化云服务的语音检测算法部署
https://www.edn.com/simplifying-the-deployment-of-voice-detection-algorithms-for-a-cloud-service/
LiveVideoStack
2021/09/01
6030
简化云服务的语音检测算法部署
让智能音箱胡言乱语、乱下指令,只需要一部手机+一个喇叭
智能音箱近两年走入了很多家庭的生活,成为了娱乐、购物、日程管理、儿童陪伴甚至教育方面的帮手。但是,智能音箱的安全问题也日益受到关注。继今年 11 月份,有研究使用激光黑掉智能音箱后,又有新的破解方法来了。这回直接用定向声波。
机器之心
2019/12/24
9390
MEMS前途无量,中国MEMS产业来日方长
不论市场面临饱和停滞,抑或高昂增长,MEMS一直以来都是半导体业的一颗璀璨明星。在即将到来的智能物联网时代,MEMS 传感器正扮演着越来越重要的角色。
AI 电堂
2020/12/08
4100
ASPINITY - 用机器学习赋能模拟电路
成立于2015年的ASPINITY,正在改变智能传感设备的商用(transforming the commercialization of intelligent sensing devices)。
用户6026865
2019/12/19
9830
ASPINITY - 用机器学习赋能模拟电路
往期精选:基于 FPGA 和麦克风阵列的高速高精度声源定位系统设计
频繁杂乱的鸣笛声,不但给周边居民的生活质量造成很大影响,而且增加了驾驶员的疲劳,影响行驶安全,并使乘客和行人在出行时倍感烦躁不安。在大多 数城市的道路上,时常出现禁止鸣笛的标志,然而并不是所有人都能自觉地遵守 规则,对鸣笛之人进行适当的处罚是确保这项规定能够顺利实施的必要举措。
FPGA技术江湖
2025/04/10
3680
往期精选:基于 FPGA 和麦克风阵列的高速高精度声源定位系统设计
推荐阅读
相关推荐
SONION - MEMS Mic骨传导拾音解决方案
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档