<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源码:
源:量子位
本文约2509字,建议阅读4分钟。
本文介绍资深前端用HTML+CSS绘画,全程不用PS、AI这种图形化的图片编辑器,单纯敲一行行代码纯手工绘制。
HTML不是编程语言,但这并不妨碍精通它的大佬玩出花来。
普通的前端,用HTML+CSS制作网页,元素简单,工具丰富。
大佬级前端,用HTML+CSS绘画,全程不用PS、AI这种图形化的图片编辑器,单纯敲一行行代码纯手工绘制。
把代码转换之后,就变成了鲜嫩的水果:
或者画出洛可可风格的古典女性肖像:
还有弗拉芒巴洛克肖像风格的人物画像,充满了中世纪的禁欲感:
现代的也有,比如这位在粉色灯光下的着礼服的妹子:
以及充满者50年代气息的复古风人物海报:
曲线、光影、渐变,每个元素都相当复杂。
而且,创作过程中不用SVG,只用Atom文本编辑器和Chrome开发者工具。
也就是说,画面上的每一条曲线和渐变、每一处高光和阴影、每一根头发和睫毛、每一片蕾丝和褶皱,都是一行行代码从头敲出来的!
如此精细程度和创造力,让学美术的网友感叹“学画画不如写代码”,让学计算机的同学觉得“别人写的这么艺术,一定是我的教科书打开方式不对”。
真·交叉学科大佬。
这个项目也一度登上了GitHub Trending排行榜第二名:
并且Issues里都是诸多用户的膜拜:厉害!崇拜!太棒了!
它们的作者,是湾区前端大神Diana Smith小姐姐,她目前是企业及软件开发商Atlassian的一名资深Web开发。
Diana在专门讨论CSS的网站CSS-Tricks写下了详细的教程。
画出这样一个图形分成几步?
如果不用CSS,一般都是直接嵌入这个特殊的图形。
如果用CSS,那么就从黑色矩形开始,然后在两侧加上上两个
与白色背景颜色匹配的边框半径元素。
先画出一个黑色矩形,然后两边用圆弧遮挡。有了基础形状后,下一步就是给它添上渐变的背景。但是如果用矩形方式填充,得到的效果就是这样的:
Diana的办法是:在保留矩形的同时,加上两个弯曲的div,把凹进去的部分也填充上。
最后完整的代码是这样的:
div{
width: 500px;
height: 350px;
background: #000;
position: relative;
&::after, &::before{
width: 20%;
height: 100%;
position: absolute;
top: 0;
z-index:2;
content: "";
background: #1e5799;
background: -moz-linear-gradient(top, #1e5799 0%, #7db9e8 100%);
background: -webkit-linear-gradient(top, #1e5799 0%,#7db9e8 100%);
background: linear-gradient(to bottom, #1e5799 0%,#7db9e8 100%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#1e5799', endColorstr='#7db9e8',GradientType=0 );
}
&::after{
border-radius: 100% 0% 0% 100%;
right: 0;
}
&::before{
border-radius: 0 100% 100% 0;
left: 0;
}
}
body{
background: #1e5799;
background: -moz-linear-gradient(top, #1e5799 0%, #7db9e8 100%);
background: -webkit-linear-gradient(top, #1e5799 0%,#7db9e8 100%);
background: linear-gradient(to bottom, #1e5799 0%,#7db9e8 100%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#1e5799', endColorstr='#7db9e8',GradientType=0 );
}
你也可以去这个完成查看CSS样式的实际运行效果:
https://codepen.io/jean-jordan/pen/KeKaBw
刚刚我们画的那幅画像不像人的脖子?好的,我们再回到人像画上,Diana绘制人物的脖子也是类似的过程。
在上面这张图里,我们看到了Diana如何逐步改形状,最终得到了油画中人物的脖子。
但是仅仅会画各种几何形状,是无法生成艺术品的,Diana总结了她在绘图中的5个重要CSS属性。
1、边界半径(border-radius)
边界半径是为了让矩形的边角过渡得更自然,对于大多数网页开发者来说,只需一个参数border-radius,可以设定不同的半径数值。
border-radius: 15px 10px 40px 30px / 40px 10px 15px 30px;
2、盒子阴影(box-shadow)
对多个盒子阴影进行分层是增加深度的最佳方法之一。框阴影将粘附到html容器的边缘,也会沿着边界半径定义的边缘。
box-shadow: 6px -11px 20px 1px red, -15px -15px 5px -10px blue, inset 5px 5px 35px 10px green;
开发者可以指定模糊半径,以及阴影是向内延伸还是向外延伸。
3、变形(transform)
变形的主要方式有:旋转(rotate)、缩放(scale)和倾斜(skew)
transform: rotate(-45deg)
transform: scale(0.7, 1.3)
transform: skew(25deg, 30deg);
此外还有透视,让物体产生远小近大的视觉效果,或者是仅仅为画出一个梯形。
transform: perspective(10px) rotateY(5deg);
4、线性梯度(linear-gradient)和径向梯度(radial-gradient)
线性梯度用于定义一个方向上的渐变效果,径向梯度用于定义圆和椭圆形的渐变效果
background-image: linear-gradient(0deg, blue, transparent 60%),
radial-gradient(circle at 70% 30%, purple, transparent 40%);
5、层叠(overflow)
层叠是一种将大量杂乱元素填充到一个整齐的包中的方法,可以创建一些有趣的形状。在变形那部分的基础上使用hidden参数,可以把边缘遮盖起来。
overflow: hidden;
以上5种元素缺一不可,随便少一种都会产生怪异的效果。
不过即使这样,也很有抽象艺术的美感,仿佛在看毕加索的作品。
不过,由于这是一个纯个人艺术创作,Diana小姐姐并不关心浏览器适配性。
因此,这些代码在Chrome里可以完美展现,但如果用其他浏览器打开,可能就会出现不一样的效果。
比如,MAC上的Safari浏览器打开,妹子的眼睛就方了:
肩膀上的高光,变成了一个大圈圈:
胸前的礼服上,也被泼了一道墨:
如果用早期的Chrome打开,会出现惊悚的头身分离的效果:
早期的Opera浏览器,打开之后脸方了:
Windows 7上从IE 6到IE 11,显示出来的都是这个鬼样子:
浓重的线条,甚至有点抽象艺术的感觉。
同样是早期IE,放到Mac上也一样鬼畜,这是IE 5.1.7的效果:
还有人试了试,在Windows 98系统的IE 7浏览器打开,会变成非常像素风的样子:
最恐怖的是三星手机上的夜间模式打开:
连人种都变了啊!
其他的几张画,换个浏览器打开也比较鬼畜。
妹子你bra里的钢圈出来了啊!
拉夫领变得透明而有光泽,领口的蕾丝干脆断掉了,仿佛是逃难时期的肖像画。
最后,如果你在iPhone上装了Chrome,出来的也是Safari的效果,想看完整效果的话,请在安卓手机或者电脑的Chrome上打开。
因此,有不少网友都觉得,这几幅画可以当成浏览器测试项目,一试就能知道内核用的是谁家的。
CSS太难,学不会?不要紧,虽然我们不能把代码变成图片,但是可以把图片变成代码啊。
没错,就是ASCII艺术,早在DOS时期,就有人用命令行界面来显示图片。直到今天已成为一种流行的互联网文化。
用单色字符来画出世界名画已经不算新鲜事。最近又有个码农开发了一个新的项目Primg,让任何一幅画都可以用质数来表示。
比如蒙拉丽莎,就可以用一个3万位的质数二进制方式绘制出来。
作者的GitHub:
https://github.com/cyanharlow
作者博客主页:
https://diana-adrianne.com/
教程:
https://css-tricks.com/solving-lifes-problems-with-css/
用质数生成任意ASCII艺术:
https://github.com/geonnave/primg
—完—
关注清华-青岛数据科学研究院官方微信公众平台“ THU数据派 ”及姊妹号“ 数据派THU ”获取更多讲座福利及优质内容。
次画的是QQ浏览器图标,比较复杂,各种高光渐变色内阴影等。其实,CSS画画就和用PS画画一样,从最底层,一层一层往上画,最后合成一张图:
CSS画QQ浏览器图标
html,body{ height:100%; } body{ display: flex; justify-content: center; align-items: center; background-color: #eee; }
.logo{ width: 300px; height:300px; background: linear-gradient(to right bottom,#66adff,#1a6adb); border-radius: 999px; position: relative; } <div class="logo"></div>
效果图:
.logo:after{ position: absolute; width: 150px; height: 150px; content:''; background-color: #eee; border-radius: 999px; z-index: 15; left: 50%; top:50%; margin-left: -75px; margin-top:-75px; }
效果图:
.logo:before{ position: absolute; width: 166px; height: 166px; content:''; background: linear-gradient(to right bottom,#1a6adb,#66adff); border-radius: 999px; z-index: 9; left: 50%; top:50%; margin-left: -83px; margin-top:-83px; }
效果图:
.before-high-light{ position: absolute; width: 168px; height: 168px; content:''; background: linear-gradient(to right bottom,#96d9ff,#5cafff,#96d9ff); border-radius: 999px; z-index: 8; left: 50%; top:50%; margin-left: -84px; margin-top:-84px; } <div class="logo"> <div class="before-high-light"></div> </div>
效果图:
.left-top-high-light{ position: absolute; width: 298px; height: 298px; content:''; background: linear-gradient(to right bottom,rgba(255,255,255,.8),rgba(255,255,255,0) 50%); border-radius: 999px; z-index: 7; left: 50%; top:50%; margin-left: -149px; margin-top:-149px; } <div class="logo"> <div class="before-high-light"></div> <div class="left-top-high-light"></div> </div>
效果图:
.clouds{ position: absolute; width: 90px; height: 90px; background-color: #fff; border-radius: 999px; z-index: 16; bottom: 20px; right:60px; box-shadow: -3px 15px 12px 0 rgba(42,127,236,.3) inset; } .clouds:before{ content:''; position: absolute; width: 100px; height: 70px; background-color: #fff; border-radius: 999px; bottom: -20px; left:-30px; box-shadow: 5px -5px 8px 0 rgba(42,127,236,.3) inset; } .clouds:after{ content:''; position: absolute; width: 123px; height: 60px; background-color: #fff; border-radius: 0 999px 999px 0; bottom: -20px; right:-30px; box-shadow: -16px -9px 11px 0 rgba(42,127,236,.3) inset; } <div class="logo"> <div class="before-high-light"></div> <div class="left-top-high-light"></div> <div class="clouds"></div> </div>
效果图:
.left-bottom-high-light{ position: absolute; width: 150px; height: 150px; content:''; background: linear-gradient(to right bottom,rgba(255,255,255,0) 50%,rgba(255,255,255,1) 70%); border-radius:0 0 0 999px; z-index: 14; left:0; bottom:0; filter: blur(1px); } <div class="logo"> <div class="before-high-light"></div> <div class="left-top-high-light"></div> <div class="clouds"></div> <div class="left-bottom-high-light"></div> </div>
效果图:
.shadow{ position: absolute; width:200px; height:10px; content:''; background:#666; border-radius:50%; z-index: 1; left: 50%; bottom:-3px; margin-left: -100px; filter: blur(4px); } <div class="logo"> <div class="before-high-light"></div> <div class="left-top-high-light"></div> <div class="clouds"></div> <div class="left-bottom-high-light"></div> <div class="shadow"></div> </div>
效果图:
.high-light{
position: absolute;
width:100px;
height:20px;
content:'';
background:rgba(255,255,255,.9);
border-radius:50%;
z-index: 15;
left: -8px;
top: 33px;
filter: blur(4px);
transform: rotate(-45deg);
}
<div class="logo">
<div class="before-high-light"></div>
<div class="left-top-high-light"></div>
<div class="clouds"></div>
<div class="left-bottom-high-light"></div>
<div class="shadow"></div>
<div class="high-light"></div>
</div>
最终效果图:
*请认真填写需求信息,我们会在24小时内与您取得联系。