前言

快过年了,辞旧迎新,无论是城市还是农村,家家户户都要贴春联。手写村联更是别有趣味,传统的纸笔必不能随身携带想写就写,鉴于此开发一套在线纸笔系统,可以随时随地掏出手机就能写春联,写的好的可以下载存到本地,甚至可以到打印社直接的打印出来。

效果展示

作品展示


需求分析

  1. 字体有粗细,写的块则细,写的慢则粗
  2. 字迹不能有断墨
  3. 字体粗细变化要平滑
  4. 可以动态调整字数
  5. 可以将写好的春联保存成图片

代码开发

使用canvas作为春联主体画布,通过鼠标或触摸事件在canvas上画曲线以写出春联,通过监听鼠标或触摸滑动的速度控制曲线的粗细以达到类似毛笔写春联的效果。

春联布局

canvas作为春联画布主体,另外设置三个按钮分别提供重置画布、修改春联字数、下载已写好的春联图片到本地。

<div id="div" style="position: relative"><canvas id="canvas">你的浏览器不支持canvas</canvas><div id="controller"><div id="clear_btn" class="op_btn" onclick ="changeNum()"> 字数</div><div id="save_btn" onclick="downloadImage()" class="op_btn">保存</div><div id="back_btn" class="op_btn" onclick="onReset()">重置</div><div class="clearfix"></div></div>
</div>

初始化春联

初始化春联canvas默认宽度为300,每个字为正方形即300 X 300,春联默认字数为7,春联上线留白50px, 春联的高度为:宽度 X 字数 + 100,代码如下。

var context;
var canvas;
var canvasWidth
var canvasHeight
//判断是否按下鼠标
var isMouseDown = false;
//记录上一次鼠标所在位置
var lasloc = {x: 0, y: 0};
var lasTimeStamp = 0;
var laslinewidth = -1;
var strokeColor = "black";
var num = 7;
$(function(){canvasWidth = 300;canvasHeight = canvasWidth * num + 100;canvas = document.getElementById("canvas");context = canvas.getContext("2d");canvas.width = canvasWidth;canvas.height = canvasHeight;drawBackGround();
})

为canvas填充红色背景,在每个字体位置标识即高5px宽10px黑色背景的div,代码如下。

//设置背景及字体位置标识
function drawBackGround(){context.fillStyle = "red";context.fillRect(0,0,canvasWidth,canvasHeight);$(".tag").remove();var y = 50;for (var i = 0; i <= num; i++) {//具体的绘制,鼠标按下之后var divElement = document.createElement("div");divElement.style.background = "black";divElement.style.position = "absolute";divElement.style.top = y + "px";divElement.style.left = "0px";divElement.style.width = "10px";divElement.style.height = "5px";divElement.setAttribute("class","tag")$("#div").append(divElement);y += canvasWidth;}
}

字体粗细设置

设置字体线宽最大20,最小1防止断墨,速度最大2最小0.1,通过速度动态计算线宽,为使字体线宽过渡平滑,每次只修改线宽的1/3,代码如下。

//笔画速度越快,笔越细,反之越粗!
var maxlinewidth = 20;
var minlinewidth = 1;
var maxlinespeed = 2;
var minlinespeed = 0.1;
//
function CalClientWidth(t, s) {var v = s / t;var ResultLineWidth;//处理速度很慢和很快的情况if (v <= minlinespeed)ResultLineWidth = maxlinewidth;else if (v >= maxlinespeed)ResultLineWidth = minlinewidth;elseResultLineWidth = maxlinewidth - (v - minlinespeed) / (maxlinespeed - minlinespeed) * (maxlinewidth - minlinewidth);if (laslinewidth == -1)return ResultLineWidth;return laslinewidth * 2 / 3 + ResultLineWidth * 1 / 3;
}

设置鼠标和触摸监听

值得注意的是,春联画布比较长,为保证书写的位置与落笔位置相匹配,在计算纵坐标时需要减去滚动条的滚动距离,代码如下。

//鼠标按下
canvas.onmousedown = function (e) {//阻止默认事件响应e.preventDefault();beginStock({x: e.clientX, y: e.clientY});
};
//鼠标松开
canvas.onmouseup = function (e) {e.preventDefault();endStock();
};
//鼠标移出指定对象时发生
canvas.onmouseout = function (e) {e.preventDefault();endStock();
};
//鼠标移动过程中
canvas.onmousemove = function (e) {e.preventDefault();if (isMouseDown) {moveStock({x: e.clientX, y: e.clientY});}
};
//移动端(触碰相关的事件)
canvas.addEventListener('touchstart', function (e) {e.preventDefault();//触碰事件,也可能是多点触碰,就是第一个let touch = e.touches[0];beginStock({x: touch.pageX, y: touch.pageY});
});
canvas.addEventListener('touchmove', function (e) {e.preventDefault();if (isMouseDown) {let touch = e.touches[0];moveStock({x: touch.pageX, y: touch.pageY});}
});
canvas.addEventListener('touchend', function (e) {e.preventDefault();endStock();
});function beginStock(point) {isMouseDown = true;lasloc = windowToCanvas(point.x, point.y - Math.max($("body").scrollTop(),document.documentElement.scrollTop));lasTimeStamp = new Date().getTime();
}function endStock(point) {isMouseDown = false;
}function moveStock(point) {var curloc = windowToCanvas(point.x, point.y - Math.max($("body").scrollTop(),document.documentElement.scrollTop));var curTimeStamp = new Date().getTime();var s = calDistance(curloc, lasloc);var t = curTimeStamp - lasTimeStamp;var lineWidth = CalClientWidth(t, s);//具体的绘制,鼠标按下之后context.beginPath();context.moveTo(lasloc.x, lasloc.y);context.lineTo(curloc.x, curloc.y);context.strokeStyle = strokeColor;context.lineWidth = lineWidth;//设置线条的帽子,是线条平滑context.lineCap = "round";context.lineJoin = "round";context.stroke();lasloc = curloc;lasTimeStamp = curTimeStamp;laslinewidth = lineWidth;
}//屏幕坐标点转化为canvas画布坐标,定位canvas画布上的坐标
function windowToCanvas(x, y) {//包含canvas距离画布的上和左边距var bbox = canvas.getBoundingClientRect();return {x: Math.round(x - bbox.left), y: Math.round(y - bbox.top)}
}//通过两点计算出两点之间距离
function calDistance(loc1, loc2) {return Math.sqrt((loc1.x - loc2.x) * (loc1.x - loc2.x) + (loc1.y - loc2.y) * (loc1.y - loc2.y));
}

设置功能按钮

逻辑比较简单,不做说明。

function downloadImage(){var image = canvas.toDataURL("image/png");var linkElement = document.createElement("a");linkElement.setAttribute('href',image);linkElement.setAttribute('downLoad','春联');linkElement.click();drawBackGround();
}function changeNum(){var s = prompt("请填写春联字数");if(!s.match(/\d+/) || new Number(s) < 1){alert("请填写正确数字")return;}num = new Number(s);canvasHeight = canvasWidth * num + 100;canvas.height = canvasHeight;drawBackGround();
}function onReset() {drawBackGround();
}

完整代码

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"><script type="text/javascript" src="${rc.contextPath}/static/js/jquery.min.js"></script>
</head>
<body>
<div id="div" style="position: relative"><canvas id="canvas">你的浏览器不支持canvas</canvas><div id="controller"><div id="clear_btn" class="op_btn" onclick ="changeNum()"> 字数</div><div id="save_btn" onclick="downloadImage()" class="op_btn">保存</div><div id="back_btn" class="op_btn" onclick="onReset()">重置</div><div class="clearfix"></div></div>
</div>
<script>var context;var canvas;var canvasWidthvar canvasHeight//判断是否按下鼠标var isMouseDown = false;//记录上一次鼠标所在位置var lasloc = {x: 0, y: 0};var lasTimeStamp = 0;var laslinewidth = -1;var strokeColor = "black";var num = 7;$(function(){canvasWidth = 300;canvasHeight = canvasWidth * num + 100;canvas = document.getElementById("canvas");context = canvas.getContext("2d");canvas.width = canvasWidth;canvas.height = canvasHeight;drawBackGround();//鼠标按下canvas.onmousedown = function (e) {//阻止默认事件响应e.preventDefault();beginStock({x: e.clientX, y: e.clientY});};//鼠标松开canvas.onmouseup = function (e) {e.preventDefault();endStock();//console.log("mouseup");};//鼠标移出指定对象时发生canvas.onmouseout = function (e) {e.preventDefault();endStock();};//鼠标移动过程中canvas.onmousemove = function (e) {e.preventDefault();if (isMouseDown) {moveStock({x: e.clientX, y: e.clientY});}};//移动端(触碰相关的事件)canvas.addEventListener('touchstart', function (e) {e.preventDefault();//触碰事件,也可能是多点触碰,就是第一个let touch = e.touches[0];beginStock({x: touch.pageX, y: touch.pageY});});canvas.addEventListener('touchmove', function (e) {e.preventDefault();if (isMouseDown) {//console.log("mousemove");let touch = e.touches[0];moveStock({x: touch.pageX, y: touch.pageY});}});canvas.addEventListener('touchend', function (e) {e.preventDefault();endStock();});})//屏幕坐标点转化为canvas画布坐标,定位canvas画布上的坐标function windowToCanvas(x, y) {//包含canvas距离画布的上和左边距var bbox = canvas.getBoundingClientRect();return {x: Math.round(x - bbox.left), y: Math.round(y - bbox.top)}}//通过两点计算出两点之间距离function calDistance(loc1, loc2) {return Math.sqrt((loc1.x - loc2.x) * (loc1.x - loc2.x) + (loc1.y - loc2.y) * (loc1.y - loc2.y));}//笔画速度越快,笔越细,反之越粗!var maxlinewidth = 20;var minlinewidth = 1;var maxlinespeed = 2;var minlinespeed = 0.1;//计算毛笔宽度function CalClientWidth(t, s) {var v = s / t;var ResultLineWidth;//处理速度很慢和很快的情况if (v <= minlinespeed)ResultLineWidth = maxlinewidth;else if (v >= maxlinespeed)ResultLineWidth = minlinewidth;elseResultLineWidth = maxlinewidth - (v - minlinespeed) / (maxlinespeed - minlinespeed) * (maxlinewidth - minlinewidth);if (laslinewidth == -1)return ResultLineWidth;return laslinewidth * 2 / 3 + ResultLineWidth * 1 / 3;}//设置背景及字体位置标识function drawBackGround(){context.fillStyle = "red";context.fillRect(0,0,canvasWidth,canvasHeight);$(".tag").remove();var y = 50;for (var i = 0; i <= num; i++) {//具体的绘制,鼠标按下之后var divElement = document.createElement("div");divElement.style.background = "black";divElement.style.position = "absolute";divElement.style.top = y + "px";divElement.style.left = "0px";divElement.style.width = "10px";divElement.style.height = "5px";divElement.setAttribute("class","tag")$("#div").append(divElement);y += canvasWidth;}}function beginStock(point) {isMouseDown = true;lasloc = windowToCanvas(point.x, point.y - Math.max($("body").scrollTop(),document.documentElement.scrollTop));lasTimeStamp = new Date().getTime();}function endStock(point) {isMouseDown = false;}function moveStock(point) {var curloc = windowToCanvas(point.x, point.y - Math.max($("body").scrollTop(),document.documentElement.scrollTop));var curTimeStamp = new Date().getTime();var s = calDistance(curloc, lasloc);var t = curTimeStamp - lasTimeStamp;var lineWidth = CalClientWidth(t, s);//具体的绘制,鼠标按下之后context.beginPath();context.moveTo(lasloc.x, lasloc.y);context.lineTo(curloc.x, curloc.y);context.strokeStyle = strokeColor;context.lineWidth = lineWidth;//设置线条的帽子,是线条平滑context.lineCap = "round";context.lineJoin = "round";context.stroke();lasloc = curloc;lasTimeStamp = curTimeStamp;laslinewidth = lineWidth;}function downloadImage(){var image = canvas.toDataURL("image/png");var linkElement = document.createElement("a");linkElement.setAttribute('href',image);linkElement.setAttribute('downLoad','春联');linkElement.click();drawBackGround();}function changeNum(){var s = prompt("请填写春联字数");if(!s.match(/\d+/) || new Number(s) < 1){alert("请填写正确数字")return;}num = new Number(s);canvasHeight = canvasWidth * num + 100;canvas.height = canvasHeight;drawBackGround();}function onReset() {drawBackGround();}
</script><style>/* CSS Document */#canvas {display: block;margin: 0 auto;border: 1px solid #aaa;}#controller {position: fixed;width: 200px;bottom: 180px;right: -90px;transform: rotate(90deg);}.op_btn {float: right;margin: 10px 0 0 10px;border: 1px solid #efefef;width: 50px;height: 25px;line-height: 25px;font-size: 12px;text-align: center;border-radius: 2px;cursor: pointer;background: #fff;}.op_btn {background-color: red;color:#fff;}.clearfix {clear: both;}
</style></body>
</html>

开发一套在线纸笔系统,随时随地在线写春联相关推荐

  1. 在线教育直播系统 一对一在线直播平台解决方案

    在线教育直播系统 一对一在线直播平台解决方案 在线教育直播系统功能模块 在线教育直播系统讲师端功能 在线教育直播系统学生端功能 一对一在线教育系统功能介绍 一对一在线教育系统优势 在线教育直播系统功能 ...

  2. 明科在线客服系统PHP_在线客服系统的标准功能有哪些

    在线客服系统一般有哪些标准的功能呢?从整体的设计结构以及系统工作的原理来讲,一般分为几大模块: 1. 渠道接入:客户有可能与企业进行接触的不同触点,或者企业规划的固定服务窗口,视为客服系统接入的渠道. ...

  3. 在线客服php技术,WeLive开源PHP在线客服系统、在线客服源码下载

    WeLive开源PHP在线客服系统 WeLive是一个企业级的在线客服系统, 程序小巧使用简单, 主要特点: 1. PHP开发, 基于WebSocket通讯技术, 具有请求与推送全双工功效, 极速高效 ...

  4. php在线投稿审稿系统,化学试剂在线投稿审稿系统

    化学试剂信息站欢迎您的到来! 经过近1年的筹划,数月的劳作,网上投稿系统现已初步建成.形成了以投稿.查询.审稿.修改.定稿于一体的业务流程.达到了方便作者投稿,查询稿件进度:方便专家审稿,节省周期和节 ...

  5. 在线网上打字系统_在线网上打字比赛软件_打字练习_中英文打字系统

    本网络在线打字系统非常适合各级各类学校.公司.企事业单位等进行打字比赛.打字练习. 本软件的功能模块有:中文练习.英文练习.中文比赛.英文比赛.指法练习.聊天打字.打字游戏.榜上有名.最新公告.成绩排 ...

  6. 桥梁在线计算机监测系统,桥梁在线监测系统

    原标题:桥梁在线监测系统 监测背景 我国是个桥梁大国,据最新数据统计,我国超过100万座公路桥梁(不含市政桥梁).影响桥梁的因素居多,人为因素.车辆长期超载.材料自身退化等,缺乏及时到位的管理养护导致 ...

  7. 桥梁在线计算机监测系统,桥梁在线监测系统解决方案.doc

    桥 梁 在 线 监 测 解决方案 目录 1.概述1 2.监测内容1 3.系统构成2 3.2 系统特点4 3.2 系统特点4 4.监测系统主要设备介绍5 5.方案15 5.1 主要案例列举15 5.2 ...

  8. 在线计算机考试系统源代码,在线考试系统 附源码和文档

    [实例简介] 在线考试系统 附源码和文档. 做毕业设计,课程设计或者正在学习相关技术知识的朋友可以下载资源学习.想继续学习相关知识的可以关注我. [实例截图] [核心代码] 在线考试系统4 └── S ...

  9. 迅为3399开发板Linux固件编译-Debian系统编译烧写

    1 获取 Debian 源码 Ubuntu.Linuxqt 和 debian 系统共用一套源码,获取 debian 源码具体参考"13.1.1 获取 Linux 源码" 2 安装 ...

最新文章

  1. 科软2020计算机科学与技术,2020新高考 报考计算机类专业怎么选科
  2. 使用git修复线上指定版本的问题
  3. SQL 新加字段查询窗口报错
  4. 史无前例的 HTML5 资源参考指南
  5. C#连接Sqlite 出现:混合模式程序集是针对“v2.0.50727”版的运行时生成的,在没有配置其他信息的情况下,无法在 4.0 运行时中加载该程序集。的解决方案...
  6. java项目经验怎么写_模具工简历项目经验怎么写
  7. jsp地址栏传中文显示乱码解决方法
  8. sersync+rsync多实例网站数据同步
  9. 【推荐】微信运营书一箩筐,微信运营手册、微信力量
  10. Vue 富文本编辑器 vue-quill-editor
  11. 服务器端网站自适应,[移动SEO]PC端和移动端最佳适配方案
  12. 【综述】A Comprehensive Survey on Graph NeuralNetworks(2)
  13. word实现奇数页页眉用本章标题,偶数页用论文标题
  14. BZOJ 1778: [Usaco2010 Hol]Dotp 驱逐猪猡
  15. 病例演讲比赛PPT模板
  16. 卡巴斯基2019安装之后电脑网速变慢
  17. javaweb超市管理系统--用户管理
  18. MySQL性能指标TPS+QPS+IOPS压测
  19. 接口开发任务完成中(待续)
  20. 华为交换机RRPP的基本配置

热门文章

  1. 关于Trinity魔兽私服搭建
  2. 高性能服务器主板,单路至尊性能 英特尔S1400FP4服务器主板
  3. 推荐的几个比较好的网站和博客
  4. TinyMCE 的音乐插件/mp3 music insert plugin
  5. 机器学习入门学习笔记(三)决策树
  6. Silverlight 版 C1OutlookBar 初体验
  7. 计算机网络基础-1-绪论
  8. 在线安装K3S集群-外部数据库
  9. php python 源码安装教程,Python安装的图文教程分享
  10. match against mysql_Mysql全文搜索match against的用法