任务描述:
1>制作一个手机端的手绘板
2>要求能有毛笔的笔锋
3>画线能有轻重感4>能重播手写过程
5>能提交保存
前面把一切准备工作都做好了,现在就涉及到了笔锋的设计了,经过多方查看,
如书道:http://www.theshodo.com/Write
人民网的手写送祝福:http://t.people.com.cn/wx/chunjie?from=singlemessage&isappinstalled=0
书道使用的是贴图,一个毛笔的点然后循环将这个点的贴图贴到canvas面板上,而人民网的则是使用圆点笔锋。
让我疑惑的是,我用radius获取不到手指的触摸面积,那他们是怎么做到画线轻重感的呢?
如果你正好进入了这两个网站,可以跟我一起试试。如果你开始慢慢的来,然后突然加速,是不是发现慢慢来的笔画粗些,而突然加速后的笔画细些?对了,就是通过手写速度控制笔画的,那么速度怎么获取呢?如果您仔细看了我前面的代码,肯定会发现有一个方法的说明"计算两点之间的距离函数,方便任务三的完成,请看下面分析"不知道您发现这句话了没有,对,就是通过距离获取速度,速度的定义是:单位时间内移动的距离,单位时间是由计算机自定义的,我们无法更改。那么久只能通过距离来获取了。
通过测试“10/dis+10”这个数据是比较靠近真实毛笔画的粗细的。
下面上具体代码:
function onTouchMove(event) {
try
{
event.preventDefault();
var last1X=event.touches[0].pageX-canvasPosition.x;
var last1Y=event.touches[0].pageY-canvasPosition.y;
var rax=document.getElementById("raX");
var ray=document.getElementById('raY');
var dis=distance(lastX,lastY,last1X,last1Y);
lastLineWidth=ctx.lineWidth;
ctx.lineWidth=10/dis+10;
drawLine(ctx,lastX, lastY, last1X, last1Y );
rax.value=lastX-event.targetTouches[0].pageX+canvasPosition.x;
ray.value=lastY-event.targetTouches[0].pageY+canvasPosition.y;
lastX=event.touches[0].pageX-canvasPosition.x;
lastY=event.touches[0].pageY-canvasPosition.y;
putNewOne(lastX,lastY,10,dis);
}
catch(err){
alert(err.description);
}
}
将上面这段函数覆盖上一篇的同名函数即可。这样实现了画笔的宽度设置,但是如果你去做了实验,就会发现这样的粗细变化很突兀。考虑到要求,想到了一个解决方案,两点之间画线,那是不是可以像下面的图这样呢?
第一个圆的位置我们都已经知道了,笔画的宽度也就是圆的半径,第二个圆的位置我们也清楚,半径也一样能获取到,那么能不能画两圆之间的外公切线并连上这四个点画个四边形,然后填充颜色不就解决这个问题了吗?
可是事情不像想象的那么美好啊,首先就这四个点的位置问题,经过多方查询,这个公式比较复杂,目前正在解决这个问题。所以如果可以欢迎大家来此提意见,或者算法有谁能做到的,也请各位大神不吝赐教。
下面是手写重播的说明了。
我们每次通过触摸来画线是不是都获取到了相应的X,Y坐标、W画笔的宽度和每次移动的距离D?
将这些数据整合起来,方法如下:
function putNewOne(x,y,w,dis){
var arr={X:x,Y:y,W:w,D:dis};
saveArr.push(arr);
}
获取到了这些数据,然后如何将它重播出来呢?
我这里是用了另外一个canvas来重播
<span style="font-size:14px;">function drawOnHead(){
var headCtx=head.getContext("2d"); //head的笔
initCtx(headCtx,0.5,0.5,"#0FA"); //初始化head的笔
for(var i=0;saveArr.length>0;i++){
var arr=saveArr.shift();
headCtx.lineWidth=arr.W;
var X=arr.X;
var Y=arr.Y;
var D=arr.D;
if(D==0){
var lx=X;
var ly=Y;
setTimeout(function (X,Y,lx,ly,headCtx){return function (){
//drawRound(headCtx,lx,ly);
drawRound(headCtx,lx,ly);
};}(X,Y,lx,ly,headCtx)
,30*i);
}else{
setTimeout(function (X,Y,lx,ly,headCtx){return function (){
//drawRound(headCtx,lx,ly);
drawLine(headCtx,X,Y,lx,ly);
};}(X,Y,lx,ly,headCtx)
,30*i);
lx=X;ly=Y;
}
}
}</span>
在第三行有一个初始化函数,如下:
function initCtx(headCtx,sx,sy,color){<span style="white-space:pre"> </span>//第一个参数是画笔,第二个是X轴上缩放的倍数如果不缩放可以填1,第三个是Y轴上缩放的倍数,第四个是画笔的颜色
headCtx.scale(sx,sy);
headCtx.strokeStyle = color;//"#000";
}
第十三行有一个setTimeOut函数,如果不懂的朋友可以去百度一下。
在这里顺便提一下,js的运行机制,js是逐行扫面运行,如果遇到了函数,不会等到函数运行完成再执行下句,而是直接执行下句,所以setTimeOut的等待时间不能是固定的,而是变化的,如果在C语言中for中增加一个sleep方法会等待sleep返回后才执行下一句,而js不是js是分多线程同时执行sleep,然后它主线程会继续往下执行。所以一定要注意这里。
下面就是最后一个任务了。
function convertCanvasToImage(canvas) {
var image = new Image();
image.src = canvas.toDataURL("image/png");<span style="white-space:pre"> </span>//将canvas上的图形转换为计算数据并赋值给image的源
var img=document.getElementById("img");<span style="white-space:pre"> </span>//获取页面中ID为img的组件
img.value=image.src;<span style="white-space:pre"> </span>//将值传给img
}
function canvasToImage(){
convertCanvasToImage(document.getElementById('canvas'));
}
由此form表单中的隐藏组件img有了值,点击提交按钮便可以上传到后台,后台是设计到php的内容就不在这个帖子提及了。如有需要,我会在另外的帖子中贴出来的。
下面防止代码不完整,贴上整个全部的代码。欢迎大家评论,改善。
<pre name="code" class="html"><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
<title>Canvas</title>
</head>
<body>
<canvas id="head"></canvas>
<div><canvas style="border:medium #000" id="canvas" >您的浏览器不支持本网页</canvas><div></div></div>
<form action="{:U('Index/upload',array('uid'=>$userid))}" method="post">
<input id="img" type="hidden" name="img" /><br />
<input type="submit" value="提交" />
<input id="clear" type="button" value="清空" />
<input id="addNew" type="button" value="增加" />
</form>
<input type="text" id="raX" />
<input type="text" id="raY" />
</body>
<script type="text/javascript" src="public/js/jquery-1.11.2.min.js"></script>
<script type="text/javascript" charset="utf-8">
/**************************************常量区***************************************/
var canvas = document.getElementById("canvas");
canvas.width=(window.innerWidth-10)/3*2;
canvas.height=(window.innerHeight)/2-50;
canvas.align="center";
var head=document.getElementById('head');
head.width=(window.innerWidth-10);
head.height=(window.innerHeight)/2-50;
var canvasPosition={x:canvas.offsetLeft,y:canvas.offsetTop};
//获取id为clear的按钮值,并增加监听点击事件。
var clear=document.getElementById('clear');
clear.addEventListener('click',clearAll,false);
//获取id为addNew的按钮值,并增加监听点击事件。
var add=document.getElementById('addNew');
add.addEventListener('click',addNew,false);
//划过的点的坐标以及各种参数
var saveArr=new Array();
//上一次触摸坐标
var lastX;
var lastY;
var startlindwidth=10;
var startsudu=1;
var ctx =canvas.getContext("2d"); //canvas的笔
ctx.lineWidth=startlindwidth;//画笔粗细
ctx.strokeStyle = "#000";//画笔颜色
//ctx.setAntiAlias(true); //抗锯齿
ctx.fillStyle = ctx.strokeStyle;
var touchable = 'createTouch' in document;
if (touchable) {
canvas.addEventListener('touchstart', onTouchStart, false);
canvas.addEventListener('touchmove', onTouchMove, false);
canvas.addEventListener('touchend', onTouchEnd, false);
}
else
{
document.onmousedown=onMouseDown;
//document.onmousemove=onMouseMove;
document.onmouseup=onMouseUp;
}
/**************************************函数区***************************************/
/******************基本功能函数*********************/
//画圆
function drawRound(ctx,x,y){
ctx.beginPath();
ctx.arc(x,y,(ctx.lineWidth-1)/2,0,Math.PI*2,true);
ctx.closePath();
ctx.fill();
}
//画线
function drawLine(ctx,startX,startY,endX,endY){
ctx.beginPath();
ctx.lineCap="round";
ctx.moveTo(startX,startY);
ctx.lineTo(endX,endY);
ctx.stroke();
}
function drawLine(ctx,startX,startY,startW,endX,endY,endW){
}
function convertCanvasToImage(canvas) {
var image = new Image();
image.src = canvas.toDataURL("image/png");
var img=document.getElementById("img");
img.value=image.src;
}
function canvasToImage(){
convertCanvasToImage(document.getElementById('canvas'));
}
function distance(x1,y1,x2,y2) {
var xdiff = x2 - x1;
var ydiff = y2 - y1;
return Math.pow((xdiff * xdiff + ydiff * ydiff), 0.5);
}
/******************监听事件函数*********************/
//鼠标被压下时事件
function onMouseDown(event){
lastX=event.clientX-canvasPosition.x;
lastY=event.clientY-canvasPosition.y;
drawRound(ctx,lastX,lastY);
putNewOne(lastX,lastY,startlindwidth,0);
document.onmousemove=onMouseMove;
}
//鼠标按钮被压下后拖动事件
function onMouseMove(event){
try{
event.preventDefault();
drawLine(ctx,lastX,lastY,event.clientX-canvasPosition.x,event.clientY-canvasPosition.y);
lastX=event.clientX-canvasPosition.x;
lastY=event.clientY-canvasPosition.y;
putNewOne(lastX,lastY,thisLineWidth,dis);
}catch(err){
alert(err.description);
}
}
//鼠标松开后的时间执行
function onMouseUp(event){
document.onmousemove=null;
canvasToImage();
}
//触摸开始事件
function onTouchStart(event) {
ctx.lineWidth=startlindwidth;
initFlag=1;
event.preventDefault();
lastX=event.touches[0].pageX-canvasPosition.x;
lastY=event.touches[0].pageY-canvasPosition.y;
putNewOne(lastX,lastY,startlindwidth,0);
drawRound(ctx,lastX,lastY);
}
//触摸滑动事件
function onTouchMove(event) {
try
{
//cxt.lineWidth;
event.preventDefault();
var last1X=event.touches[0].pageX-canvasPosition.x;
var last1Y=event.touches[0].pageY-canvasPosition.y;
var rax=document.getElementById("raX");
var ray=document.getElementById('raY');
var dis=distance(lastX,lastY,last1X,last1Y);
lastLineWidth=ctx.lineWidth;
ctx.lineWidth=10/dis+10;
drawLine(ctx,lastX, lastY, last1X, last1Y );
rax.value=lastX-event.targetTouches[0].pageX+canvasPosition.x;
ray.value=lastY-event.targetTouches[0].pageY+canvasPosition.y;
lastX=event.touches[0].pageX-canvasPosition.x;
lastY=event.touches[0].pageY-canvasPosition.y;
putNewOne(lastX,lastY,10,dis);
}
catch(err){
alert(err.description);
}
}
function onTouchEnd(event){
drawRound(ctx,lastX,lastY);
canvasToImage();
//alert(saveArr);
}
/******************其他函数*********************/
function drawOnHead(){
var headCtx=head.getContext("2d"); //head的笔
initCtx(headCtx,0.5,0.5,"#0FA"); //初始化head的笔
for(var i=0;saveArr.length>0;i++){
var arr=saveArr.shift();
headCtx.lineWidth=arr.W;
var X=arr.X;
var Y=arr.Y;
var D=arr.D;
if(D==0){
var lx=X;
var ly=Y;
setTimeout(function (X,Y,lx,ly,headCtx){return function (){
//drawRound(headCtx,lx,ly);
drawRound(headCtx,lx,ly);
};}(X,Y,lx,ly,headCtx)
,30*i);
}else{
setTimeout(function (X,Y,lx,ly,headCtx){return function (){
//drawRound(headCtx,lx,ly);
drawLine(headCtx,X,Y,lx,ly);
};}(X,Y,lx,ly,headCtx)
,30*i);
lx=X;ly=Y;
}
}
}
function putNewOne(x,y,w,dis){
var arr={X:x,Y:y,W:w,D:dis};
saveArr.push(arr);
}
function initCtx(headCtx,sx,sy,color){
headCtx.scale(sx,sy);
headCtx.strokeStyle = color;//"#000";
}
//增加canvas字
function addNew(){
var board=document.getElementById('board');
var img = document.createElement('img');
img.src=canvas.toDataURL("image/png");
img.width=(window.innerWidth-10)/4;
var object=board.appendChild(img);
//alert(canvas.offsetLeft+canvas.offsetTop);
}
//清空canvas面板
function clearAll(name){
drawOnHead();
ctx.clearRect(0,0,window.innerWidth-10,window.innerHeight-100);
saveArr=new Array();
}
</script>
</html>