承上启下:上篇我们学过了canvas中绘图的颜色阴影以及渐变操作,这些有时候是给我们绘图锦上添花有时候是审美需要,无论如何 都是canvas不可获取的一部分,
canvas中还有一些操作可以为你实现比较复杂的效果例如文本书写、剪裁缩放、以及图像合成、滤镜通道(后续)等。这篇文章主要为大家介绍一下文本以及剪裁缩放。
文本
文本的描边与填充在实际操作时候跟其他基本图形一样,只是调用方法有所不同,文本有自己一套方法以及属性,例如我们上一篇说到的font属性以及fillText方法。但是这些远远不够我们需求,
大部分情况下我们需要文字有颜色而且位置根据需求来定,显示效果也有所不同。我们来看看这些都是怎么实现的。
1.文本描边与填充渐变:
context.strokeStyle='rgba(100,100,100,0.3)';
context.font='25pt 微软雅黑';
context.lineWidth=1;
context.strokeText("hello Canvas",200,canvas.height/2);
以上是给文本描边,描边颜色我们选择透明的,字体属性选择25号字体微软雅黑 字体边1像素,然后用strokeText方法在200,二分之一高度为起始点写下hello Canvas几个字母
填充也是类似:
var gra=context.createLinearGradient(200,0,canvas.width,0);
gra.addColorStop(0,'blue');
gra.addColorStop(0.25,'white');
gra.addColorStop(0.5,'purple');
gra.addColorStop(0.75,'red');
gra.addColorStop(1,'yellow');
context.fillStyle=gra;
context.font='25pt 微软雅黑';
context.fillText('hello Canvas',200,canvas.height/2);
在填充时候我们利用渐变效果进行填充,并把渐变效果赋值给fillStyle属性,代替颜色填充,填充时候可以用颜色渐变甚至图案,这里就不再赘叙了,同理在描边时候也可以用渐变来进行描边,大家可以也试试。
2.设置字型属性:
我们可以通过font属性来设置canvas中的字体所采用字形,该属性是一个css3格式的字符串,接受各个分量如下所示,开发时候设置属性值时候需要按照由上至下顺序来依次指定这些分量值:
字形属性分量 | 有效取值 |
font-style | normal、italic.oblque |
font-variant | normal、small-caps |
font-weight | 决定字符笔画粗细 可取normal(400)bold、bolder、lighter或者数字100、200... |
font-size | 字形大小:xx-small、xsmall、medium、large、x-large、xx-large、smaller、larger、length(px等单位固定量)或% |
line-height | 强制默认normal |
font-family | 仿照css3取值:如palatino |
Canvas默认字型是10px sans-seif。font-style、font-variant与font-weight默认值均为normal
我们写一段代码多测试几种样式如下:
var leftArr=['2em palatino','bolder 2em palatino','xx-large palatino','oblique 1.5em lucida console','x-large fantasy','28px tahoma'];
var y=50;
leftArr.forEach(function(font){
context.font=font;
context.fillText(font,25,y+=50)
});
各种效果如下图:
各种组合不能一下子都组合完,大家可以根据需要去查一下文档 或者根据业务自己定制字形,如果font属性值无效或者弄反分量顺序,浏览器不会报错而是恢复默认值,赋值作废。
3.文本定位:
我们学会了如何对文本进行填充描边以及如何设置字形,然后我们来看看canvas如何对文本进行定位:
在使用fillText与strokeText绘制文本时候,需要指定文本xy坐标,然而具体会将文本绘制在什么地方要看textAlign与textBaseline两个属性的值。
textAlign定义水平方向上对齐方式可取值有start、center、end、left、right.默认值为start,其中start与left和right与end效果是一样的,如果我们在canvas元素定义dir属性是ltr那么就是从左往右如果该属性值为rtl那么将是按照从右向左计算起始点。
textBaseline定义垂直方向上对齐方式属性值可以取值为:top、bottom、middle、alphabetic、ideographic、hanging默认值为alphabetic(该值跟小学用三格本写字母一样规则),
ideographic用于日文或者中文规则,hanging用于印度语言,top与bottom以及middle定义文本周围边界框的某个位置。例如top的话我们文字则在定义坐标的下边。也就是定义坐标在文字top方向。
我们来像设置文本属性一样遍历数组来看几种定位文本:
var fontHeight=25,alignVal=['start','center','end'],baseVal=["top",'middle','bottom','alphabetic','ideographic'],x,y;
context.font="oblique normal bold 15px palatino";
for(var i=0;i<alignVal.length;++i){
for(var j=0;j<baseVal.length;++j){
x=20+fontHeight*i*10;
y=20+fontHeight*j*3;
drawText(alignVal[i]+"/"+baseVal[j],alignVal[i],baseVal[j]);
drawTextMarker();
drawTextLine();
}
}
function drawText(text,align,base){
if(align){context.textAlign=align}
if(base){context.textBaseline=base}
context.fillStyle='cornflowerblue';
context.fillText(text,x,y)
}
function drawTextMarker(){
context.fillStyle='red';
context.fillRect(x,y,5,5);
context.strokeRect(x,y,5,5);
}
function drawTextLine(){
context.strokeStyle='gray';
context.beginPath();
context.moveTo(x,y);
context.lineTo(x+400,y);
context.stroke();
}效果如下:
可以想象一下我们如果想把文本居中在某一点 例如canvas中心,应该怎么做?很简单 只要fillText(text,canvas.width/2,canvas.height/2);指定对齐方式为context.textAlign='center';context.textBaseline='middle'即可
在文字操作中我们有时候需要获得一段文本或者某个字母的宽度,于是乎就有了一个方法:measureText(),该方法接受一个参数,要检测的字符串或者字符,然后返回一个包含width属性的对象。
于是我们可以在上面检测每段text的宽度:context.measureText(text).width;
我们可以试着用以上方法文本属性结合js做两个练习,一个是画坐标轴,第二个在圆弧周围绘制文字:
第一个大家自己试着做吧,我们来一起看看第二个是如何实现的:
这段代码利用大量数学中三角函数计算,忘了的同学可以复习一下.ha
var fillstyle="(100,100,100,0.8)",strokestyle='rgba(200,100,100,0.5)',textsize=64,circle={x:canvas.width/2,y:canvas.height/2,radius:150};
function drawCircleText(string,start,end){
var radius=circle.radius,
angleCre=(start-end)/(string.length-1),
angle=parseFloat(start),
character;
context.save();
context.fillStyle=fillstyle;
context.strokeStyle=strokestyle;
context.font=textsize+'px palatino';
for(var index=0;index<string.length;index++){
character=string.charAt(index);
context.save();
context.beginPath();
context.translate(circle.x+Math.cos(angle)*radius,circle.y-Math.sin(angle)*radius);
context.rotate(Math.PI/2-angle);
context.fillText(character,0,0);
context.strokeText(character,0,0);
angle-=angleCre;
context.restore();
}
context.restore();
}
context.textAlign='center';
context.textBaseline='middle';
drawCircleText("hello yuchao Canvas",Math.PI*2,Math.PI/8);
效果如下:
在这段程序中我们又看到两个陌生的方法:translate以及rotate。这两个方法本来是跟文本这篇没有直属关系,不过要实现我们环绕效果还是得讲一下:
roate(angle):旋转当前绘图,参数旋转角度:例如旋转5度,5*Math.PI/180;
translate(x,y):重新映射当前绘图,坐标(0,0)为参照,配合fillRect等使用生效,并fillRect基础上进行复制映射到新位置。
例如我们画一个矩形(10,10,100,100)然后translate(90,90);然后再同样画矩形就以(90.90)这个点作为0,0原点,也就是实际开始点为100,100。
如果大家记起来三角函数的话 我们可以很简单的绘制一个时钟。不妨试一下。这是我的截图如下:
ok,文本就讲到这,大家可以发散思维想象一下以现有知识如何制作一个简单文本编辑器。。ok下面说说剪裁缩放
裁剪缩放
要说到剪裁缩放就有了一个新的方法:drawImage
drawImage会将一副图像绘制到canvas中,所绘制的图像叫做“源图像”,而要绘制到的地方叫做“目标canvas”,该方法接受以下三套参数:
第一套
drawImage(image,x,y):将目标图像整个绘制到指定坐标点的位置上。
第二套
drawImage(image,dx,dy,dw,dh):会将图像完整绘制到canvas上,不过在绘制时候会根据目标区域的dw以及dh设置对宽高进行缩放。
第三套
drawImage(image,sx,sy,sw,sh,dx,dy,dw,dh):这个方法就可以将整幅图像或一部分绘制到canvas指定位置,并且在绘制时候根据设置宽高进行缩放。
这三套方式的第一个参数可以是我们的image也可以是一个HTMLCanvasElement类型的canvas对象。或者video对象,所以我们也可以将canvas或者视频对象当图像来使用。
于是我们看明白了,操作图像方法有这三种,第三种接受的参数最多,功能也最强大,就是它作为我们剪裁缩放的中心方法。
我们来做一个例子,利用页面中的html元素配合canvas完成一个支持手动的剪裁操作。
先上段代码:
<style>
body{
background:rgba(10,145,50,0.5);
}
#canvas{
margin-left:20px;
margin-right:0;
margin-bottom:20px;
border:thin solid #aaaaaa;
cursor:crosshair;
padding:0;
}
#controls{
margin:20px 0px 20px 20px;
}
#bindDiv{
position:absolute;
border:3px solid blue;
cursor:crosshair;
display:none;
}
</style>
<body>
<div id="controls">
<input type="button" id="resetButton" value="Reset">
</div>
<div id="bindDiv"></div>
<canvas id="canvas" width="600" height="400" style="border:1px solid #d3d3d3;">
<p>Your browser does not support the canvas element.</p>
</canvas>
</body>
我们借用在html中定义的一个bindDiv来作为我们剪裁的参照矩形框,所剪裁的图像就在这个轮廓之内,并且定义一个button按钮使之恢复。
var canvas=document.getElementById("canvas"),
context=canvas.getContext("2d"),
bindDiv=document.getElementById("bindDiv"),
button=document.getElementById('resetButton'),
image=new Image(),
mousedown={},//鼠标按下时候坐标
bandRectangle={},//定义矩形的左上角以及宽高
dragging=false;//是否开始裁剪
image.src="gg.jpg";
//按钮按下复位
button.οnclick=function(){
context.clearRect(0,0,canvas.width,canvas.height);//擦掉整个画布,忘了说没说过了
context.drawImage(image,0,0,canvas.width,canvas.height);
}
//鼠标按下开始定义起始点
canvas.οnmοusedοwn=function(e){
var x=e.clientX,
y=e.clientY;
e.preventDefault();
bandStart(x,y);
};
//鼠标移动选区
window.οnmοusemοve=function(e){
var x=e.clientX,
y=e.clientY;
e.preventDefault();
if(dragging){
bandStretch(x,y);
}
};
//抬起鼠标完成选区
window.οnmοuseup=function(e){
e.preventDefault();
bandEnd();
}
//加载图片后
image.οnlοad=function(){
context.drawImage(image,0,0,canvas.width,canvas.height);
}
整理一下思路:我们先定义几个动作以及图片加载,在按下鼠标时候记录下当前鼠标坐标,因为用户不一定拉到左右方,所以再移动时候要重新定位起始点在所截取的图片位置(可能是按下点的左边呢)。
同时在拖动时候显示我们的div元素在图片之上,并且动态的根据鼠标改变大小。当放下鼠标时候说明选区结束,可以裁剪了。ok我们来看看代码如何实现://选区起始点
function bandStart(x,y){
mousedown.x=x;
mousedown.y=y;
bandRectangle.left=mousedown.x;
bandRectangle.top=mousedown.y;
moveBandDiv();
showBandDiv();dragging=true;
}//选区左上角
function moveBandDiv(){
bindDiv.style.left=bandRectangle.left+'px';
bindDiv.style.top=bandRectangle.top+'px';
}
//显示选区框
function showBandDiv(){
bindDiv.style.display='inline';
}
//鼠标移动选区变化
function bandStretch(x,y){
bandRectangle.left=x<mousedown.x?x:mousedown.x;
bandRectangle.top=y<mousedown.y?y:mousedown.y;
bandRectangle.width=Math.abs(x-mousedown.x);
bandRectangle.height=Math.abs(y-mousedown.y);
moveBandDiv();
resizeBandDiv();
}
function resizeBandDiv(){
bindDiv.style.width=bandRectangle.width+'px';
bindDiv.style.height=bandRectangle.height+"px";
}
//选区结束后裁剪选区内图像并填满整个画布
function bandEnd(){
var bbox=canvas.getBoundingClientRect();
try{
//debugger;
context.drawImage(canvas,bandRectangle.left-bbox.left,bandRectangle.top-bbox.top,
bandRectangle.width,bandRectangle.height,0,0,canvas.width,canvas.height);
}
catch(e){
//当鼠标移除canvas时候会报错
}
resetBandRectangle();
bindDiv.style.width=0;
bindDiv.style.height=0;
hideBandDiv();
dragging=false;
}
//重置选区
function resetBandRectangle(){
bandRectangle={top:0,left:0,width:0,height:0};
}
//隐藏选区
function hideBandDiv(){
bindDiv.style.display="none";
}
我们实现的效果是将图片剪裁后立即填充满整个画布,看看效果吧:
第一个图片我们选择选区 松开鼠标后选区填充整个canvas,点击按钮就可以恢复原图。
ok,我们的文本以及裁剪缩放讲到这,在过程中遇到一些辅助函数例如:translate以及roate等,只有将这些综合起来才能更好的满足我们实际需求。
前几篇在canvas中算是基本用法,下一篇开始我们来一起学习些比较高级点的东西。例如:贝塞尔曲线、多边形、图像合成离屏canvas以及图像与视频动画等。。。挺诱人的吧。敬请期待哦!