有没有很好奇,类似 processOn 这类作图网站中,网格背景 是怎么做的呢?
网格背景
如果你 F12 看过它的代码,你将发现原来不是通过 background-image 之类 css 属性做的,而是通过 canvas 实现的。
网格背景
这篇主要讲如何通过 canvas 来绘制出这样的 网格背景,以及中间碰到的一个 线条模糊的问题。
1. 创建 ctx 对象
这边将新建一个和屏幕尺寸相同的 canvas 画布:
const canvasEl = document.createElement("canvas");
const sh = screen.height;
const sw = screen.width;
canvasEl.width = sw;
canvasEl.height = sh;
const ctx = canvasEl.getContext("2d");
2. 绘制网格
先简单说下思路:
根据 canvas 画布尺寸,以 10px 为间距,分别绘制横纵坐标的线条。
比如绘制横向线条,先将“画笔”移至起点坐标 (0, y),然后通过线条方法 lineTo 绘制屏幕宽度的线条,即绘制 (0, y) 至 (screen.width, y) 的直线。
然后 y 会按照间距 10px 逐渐递增,直至绘制完整个屏幕。
下面根据横纵两个方向,封装了 draw 方法:
const draw = function (isColumn) {
const gutter = 10;
const limit = isColumn ? sh : sw;
let i = 0;
while (i * gutter + gutter <= limit) {
i++;
const point = i * gutter;
// 清空子路径列表开始一个新路径
ctx.beginPath();
// 分割线
ctx.strokeStyle = point % 100 !== 0 ? "#f0f0f0" : "#d6e4ff";
// 将一个新的子路径的起始点移动到(x,y)坐标
if (isColumn) {
ctx.moveTo(0, point);
} else {
ctx.moveTo(point, 0);
}
// 使用直线连接子路径的终点到x,y坐标
if (isColumn) {
ctx.lineTo(sw, point);
} else {
ctx.lineTo(point, sh);
}
// 根据当前的画线样式,绘制当前或已经存在的路径的方法
ctx.stroke();
}
};
然后分别绘制横纵坐标,并把 canvas 挂载到 body 上:
draw(true);
draw(false);
document.body.append(canvasEl);
很顺利你将得到 canvas 绘制的网格图形:
网格背景
如果你观察比较细腻,能发现上面的网格图形并不是很清晰,可以仔细观察下面的图:
对比效果(左边模糊,右边清晰)
为什么会有这样的情况出现呢?
因为调用 lineTo 是从 A 点到 B 点,轨迹是点到点的连线。当绘制 1px 线条时,是以这个轨迹连线为中间线左右各渲染 0.5px 的线条。这会使得一个像素点只渲染了一半,而另一半会用一个比较弱的颜色填充,导致绘制出模糊的线条。
怎么改?
只要在 moveTo 时,将坐标偏移 0.5px(即将线条轨迹偏移 0.5 位置),然后调用 lineTo 时,也将坐标偏移 0.5px。
这样,最终绘制线条的时候,将在一个完整的像素区域进行渲染了。
选自:https://www.jianshu.com/p/c0970eecd843
有没有便捷的方法?
使用 translate 偏移 x 和 y 坐标值,然后绘制好后再调用 setTransform 重置回来:
ctx.translate(0.5, 0.5);
//...
ctx.setTransform(1, 0, 0, 1, 0, 0);
#前端开发#
<canvas> 是HTML中的一个元素,它可被用来通过 JavaScript(Canvas API 或 WebGL API)绘制图形及图形动画。
Canvas API 提供了一个通过 JavaScript 和 HTML 的 <canvas> 元素来绘制图形的方式。它可以用于动画、游戏画面、数据可视化、图片编辑以及实时视频处理等方面。
<canvas>标签本身没有绘图能力,它仅仅是图形的容器。在HTML,一般通过Javascript语言来完成实际的操作。
本文通过Javascript操作Canvas制作一个简单的显示当前时间的动画时钟,了解和学习简单的canvas用法,仅以抛砖引玉。
首先创建一个HTML文件,为了方便管理,使用一个div标签包裹两个canvas标签,并加上一些简单的css样式。
<!doctype html>
<html lang="zh-cn">
<head><title>Canvas绘制动画时钟</title>
<style>
html,body{margin:0;padding:0}
#clockWrap {
position: relative;
}
canvas {
position: absolute;
}
#clock-ui {
z-index: 2;
}
#clock-plate {
z-index: 1;
}
</style>
</head>
<body>
<div id="clockWrap">
<canvas id="clock-plate"></canvas>
<canvas id="clock-ui"></canvas>
</div>
<script></script>
</body></html>
本示例中使用了两个canvas标签(为什么使用两个,一个不是更简单吗?),一个用于绘制钟面,一个根据当前时间实时显示和更新时针、分针和秒针的动态指向。好了,话不多说,开干。
一个简单的时钟,可以分为钟面上的刻度和指针。其中刻度和12个数字是固定的,我们可以将它们绘制在当作背景的canvas上(示例中id为clock-plate的canvas)。
(1)要使用canvas,首先必须通过容器获取渲染上下文:
var $=function(id){return document.querySelector(id);}//这个函数只是为了方便获取dom元素
const canvasbg=$("#clock-plate"),
canvas=$("#clock-ui"),
ctx = canvasbg.getContext("2d"),//背景容器上下文
ctxUI = canvas.getContext("2d");//指针容器上下文,后面代码要用
//定义画布宽度和高度,时钟圆直径,并设置画布大小
const oW=1000,oH=800,cW=400,r=cW/2,oX=oW/2,oY=oH/2;
canvas.width=oW;
canvas.height=oH;
canvasbg.width=oW;
canvasbg.height=oH;
(2)画钟的边框,为了好看,这里画两个圈:
//画出时钟外圆框
ctx.lineWidth = 12;
ctx.beginPath();
ctx.arc(oX, oY, r+14, 0, 2 * Math.PI);
ctx.stroke();
ctx.closePath();
ctx.lineWidth = 8;
//画出时钟内圆框(刻度圈)
ctx.beginPath();
ctx.arc(oX, oY, r, 0, 2 * Math.PI);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
边框效果图
(3)绘制刻度线和数字,可以利用三角函数计算出每个刻度的坐标:
利用三角函数计算刻度线的坐标位置
钟面上有12个大格,从正上方12开始,它们的度数分别是270,300,330,0,30,60,90,120,150,180,210,240。然后利用JS的Math.sin和Math.cos分别计算出各大格的坐标。注意:js中Math.sin()和Math.cos()的参数不是角度数是弧度。可以使用Math.PI/180*角度来转化,比如将30度转换成弧度=Math.PI/180*30
//绘制钟表中心点
ctx.beginPath();
ctx.arc(oX, oY, 8, 0, 2 * Math.PI);//圆心
ctx.fill();
ctx.closePath();
//设置刻度线粗细度
ctx.lineWidth = 3;
//设置钟面12个数字的字体、大小和对齐方式
ctx.font = "30px serif";
ctx.textAlign="center";
ctx.textBaseline="middle";
var kdx,kdy;
//绘制12个大刻度和12个数字
//为方便计算,先定义了0-11这12个刻度对应的度数,也可以直接定义对应的弧度。
const hd=Math.PI/180,degr=[270,300,330,0,30,60,90,120,150,180,210,240];
for(var i=0;i<12;i++){
kdx=oX+Math.cos(hd*degr[i])*(r-3);
kdy=oY+Math.sin(hd*degr[i])*(r-3);
ctx.beginPath();
ctx.arc(kdx, kdy, 6, 0, 2 * Math.PI);//画圆形大刻度
ctx.fill();
//绘制刻度对应的数字
ctx.strokeText(i==0? 12 : i,oX+Math.cos(hd*degr[i])*(r-24),oY+Math.sin(hd*degr[i])*(r-24));
ctx.closePath();
}
//绘制小刻度
ctx.lineWidth = 2;
for(var i=0;i<60;i++){
if(i % 5 == 0) continue;//跳过与刻度重叠的刻度
x0=Math.cos(hd*i*6);
y0=Math.sin(hd*i*6);
ctx.beginPath();
ctx.moveTo(oX+x0*(r-10), oY+y0*(r-10));
ctx.lineTo(oX+x0*r, oY+y0*r); //画短刻度线
ctx.stroke();
ctx.closePath();
}
效果如图:
钟面效果图
(4)根据当前时间绘制指针
习惯上,时针粗短,分针略粗而长,秒针细长。为加大区别,示例中秒针细长并且绘制成红色。
function drawHp(i){//绘制时针
const x0=Math.cos(hd*i*30),y0=Math.sin(hd*i*30);
drawPointer(oX,oY,oX+x0*(r-90),oY+y0*(r-90),10,"#000000");
}
function drawMp(i){//绘制分针
const x0=Math.cos(hd*i*6),y0=Math.sin(hd*i*6);
drawPointer(oX,oY,oX+x0*(r-60),oY+y0*(r-60),5,"#000000");
}
function drawSp(i){//绘制秒针
const x0=Math.cos(hd*i*6),y0=Math.sin(hd*i*6);
drawPointer(oX,oY,oX+x0*(r-20),oY+y0*(r-20),2,"#FF0000");
}
//抽取出绘制三种指针时共同的部分,注意指针绘制在渲染上下文ctxUI中
function drawPointer(ox,oy,tx,ty,width,color){
ctxUI.strokeStyle = color;
ctxUI.lineCap = "round";
ctxUI.lineWidth = width;
ctxUI.beginPath();
ctxUI.moveTo(ox, oy);
ctxUI.lineTo(tx,ty);
ctxUI.stroke();
ctxUI.closePath();
}
现在已经有了绘制三种指针的方法,参数是当前时间的时、分和秒,将根据它们的值确定指针的坐标。不过,因为使用的是默认的convas坐标体系,0值实际指向3的位置,需要小小的修正一下。
window.requestAnimationFrame(function fn(){
var d = new Date();
ctxUI.clearRect(0,0,oW,oH);
//度数从0开始,而0在3刻度(15分/秒位置),修正为全值减15,如果小于0则修正回来
var hour=d.getHours(),minute=d.getMinutes()-15,second=d.getSeconds()-15;
hour=hour>11? hour-15 : hour-3;
drawHp(hour>=0? hour : 12+hour);
drawMp(minute>=0? minute : 60+minute);
drawSp(second>=0? second : 60+second);
window.requestAnimationFrame(fn);
});
接下来,调用window.requestAnimationFrame,在其中绘制并更新指标的位置。看看效果如何:
指针绘制情况与实际时间相符
貌似效果有了,截图时电脑上的时间是10时17分,指针绘制上,时针指向10时,分针指向17。嗯,感觉有点别扭?对了,时针和分针怎么是端端正正地指向它们的整时整分刻度上呢?实际钟表上时针和分针是展示动态进度的,此时时针应该越过10时的位置才对。没关系,我们在绘制时针和分针时别点东西,让它的角度值加上分针和秒针的值试试。
//修改后的绘制三种指针的方法
function drawHp(i,f,m){//绘制时针,参数:时,分,秒
const x0=Math.cos(hd*(i+(f/60)+(m/600))*30),y0=Math.sin(hd*(i+(f/60)+(m/600))*30);
drawPointer(oX,oY,oX+x0*(r-90),oY+y0*(r-90),10,"#000000");
}
function drawMp(i,f){//绘制分针,参数,分,秒
const x0=Math.cos(hd*(i+(f/60))*6),y0=Math.sin(hd*(i+(f/60))*6);
drawPointer(oX,oY,oX+x0*(r-60),oY+y0*(r-60),5,"#000000");
}
function drawSp(i){//绘制秒针
const x0=Math.cos(hd*i*6),y0=Math.sin(hd*i*6);
drawPointer(oX,oY,oX+x0*(r-20),oY+y0*(r-20),2,"#FF0000");
}
再来看看效果,嗯,立竿见影呀:
指针指向更合理了
到此为止,canvas绘制一个简易时钟就完成了。下面继续优化一下。刚才使用requestAnimationFrame方法即时更新绘制情况。这个方法与刷新率有关,看看mdn上面怎么说的:
window.requestAnimationFrame() 方法会告诉浏览器你希望执行一个动画。它要求浏览器在下一次重绘之前,调用用户提供的回调函数。
对回调函数的调用频率通常与显示器的刷新率相匹配。虽然 75hz、120hz 和 144hz 也被广泛使用,但是最常见的刷新率还是 60hz(每秒 60 个周期/帧)。为了提高性能和电池寿命,大多数浏览器都会暂停在后台选项卡或者隐藏的 <iframe> 中运行的 requestAnimationFrame()。
本示例中,更新指针的位置并不需要很高的刷新频率,可以通过节流进行一下优化:
var fps = 5, fpsInterval = 1000 / fps,lastTime = new Date().getTime(); //记录上次执行的时间
function runStep() {
requestAnimationFrame(runStep);
var d=new Date(),now = d.getTime()
var elapsed = now - lastTime;
if (elapsed > fpsInterval) {
ctxUI.clearRect(0,0,oW,oH);
lastTime = now - (elapsed % fpsInterval);
//度数从0开始,而0在3刻度(15分/秒位置),修正为全值-15,如果小于0则用60减回
var hour=d.getHours(),minute=d.getMinutes()-15,second=d.getSeconds()-15;//console.log(d.getSeconds(),second);
hour=hour>11? hour-15 : hour-3;
drawHp(hour>=0? hour : 12+hour,minute+15,second+15);
drawMp(minute>=0? minute : 60+minute,second+15);
drawSp(second>=0? second : 60+second);
}
}
runStep();
当然,实现时钟的方法是很多,比如可以使用画布的旋转(rotate方法)来实现指针的动态转动等等。
完整HTML+JS源码:
随着HTML5的火热,越来越多的人投入到HTML5开发中了,canvas作为HTML5中比较重要的一个元素,在很多官网的主页面中被使用到。今天我们一起来看看如何使用canvas画出一个梦幻的星空背景,还会有流星运动。
本文的代码已经放到Github上了,感兴趣的可以自取,Github地址如下。
https://github.com/zhouxiongking/article-pages/blob/master/articles/starry/starry.html
HTML5
首先我们来看看通过canvas实现的星空效果图,如下所示。
效果图
接下来我们看看这个效果是如何通过代码一步步实现的。
首先来看看页面上的HTML代码,只有一个Div元素。
HTML代码
首先我们需要定义一些常量,比如画布的宽和高,星星数量,流星个数。在这个星空中流星其实是星星的一个,只是添加了动态效果。
页面初始化
然后是设定一个定时器,通过一段随机时间生成一个流星的索引号。
流星索引号
紧接着来看看生成一个星星的方法,该方法返回一个星星的各项参数,包括x,y轴坐标,透明度,x,y轴偏移量。
生成星星的参数
然后是最重要的render方法,通过该方法可以将星星渲染至画布上,我们将这个方法拆开看,首先是对流星的绘制,流星索引号通过上面metor方法获得。
画流星
然后是对于星星各项参数的处理,比如有的星星生成的点坐标超出了屏幕宽高,有的透明度是负数,都要将其处理成正常参数。
各项参数判断
最后是在画布中进行绘制。
画布绘制
至此,这个画面效果的讲解完毕,如果代码正确的话,就可以看到文中出现的效果图。
今天这篇文章主要是借助HTML5中的canvas画出了一个梦幻星空的效果,你学会了吗?
*请认真填写需求信息,我们会在24小时内与您取得联系。