anvas画了一个圆
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>canvas-3圆的绘制</title>
</head>
<body>
<canvas id="canvas1" width="600" height="600" style="border:1px solid #000000"></canvas>
<script type="text/javascript">
var canvas1=document.querySelector("#canvas1") // 1.找到画布对象
var ctx=canvas1.getContext("2d") // 2.上下文对象(画笔)
// (圆心x:300, 圆心y:300, 半径r:100, 开始角度:0 , 结束角度:360度, 默认为false(可不写)是顺时针,true为逆时针)
ctx.arc(300, 300, 100, 0, 2*Math.PI)
ctx.stroke()
</script>
</body>
</html>
家好! 欢迎来到本教程,我们将深入了解使用 HTML 画布和 JavaScript 在代码中创建有趣的气泡的世界。 最好的部分? 我们将只使用一点 HTML 和所有 JavaScript,而不是 CSS 来实现所有这一切。
今天,我们要掌握以下几个概念:
使用画布上下文的 arc 方法创建圆。
利用 requestAnimationFrame 函数实现平滑的圆形动画。
利用 JavaScript 类的强大功能来创建多个圆圈,而无需重复代码。
向我们的圆圈添加描边样式和填充样式以获得 3D 气泡效果。
你可以跟着我一起看,或者如果你想看源代码,可以使用最终的codepen
首先,我们需要一个 HTML5 Canvas 元素。 Canvas 是创建形状、图像和图形的强大元素。 这就是气泡将产生的地方。 让我们来设置一下 -
<canvas id="canvas"></canvas>
为了使用画布做任何有意义的事情,我们需要访问它的上下文。 Context 提供了在画布上渲染对象和绘制形状的接口。
让我们访问画布及其上下文。
const canvas=document.getElementById('canvas');
const context=canvas.getContext('2d');
我们将设置画布以使用整个窗口的高度和宽度 -
canvas.width=window.innerWidth;
canvas.height=window.innerHeight;
让我们通过添加一些 css 为画布提供一个漂亮的舒缓浅蓝色背景。 这是我们要使用的唯一 CSS。 如果您愿意,也可以使用 JavaScript 来完成此操作。
#canvas {
background: #00b4ff;
}
让我们进入有趣的部分。 我们将通过单击画布来创建气泡。 为了实现这一点,我们首先创建一个点击事件处理程序:
canvas.addEventListener('click', handleDrawCircle);
由于我们需要知道在画布上单击的位置,因此我们将在句柄 DrawCircle 函数中跟踪它并使用事件的坐标 -
//We are adding x and y here because we will need it later.
let x, y
const handleDrawCircle=(event)=> {
x=event.pageX;
y=event.pageY;
// Draw a bubble!
drawCircle(x, y);
};
为了创建圆圈,我们将利用画布上下文中可用的 arc 方法。 Arc 方法接受 x 和 y - 圆心、半径、起始角和结束角,对于我们来说,这将是 0 和 2* Math.PI,因为我们正在创建一个完整的圆。
const drawCircle=(x, y)=> {
context.beginPath();
context.arc(x, y, 50, 0, 2 * Math.PI);
context.strokeStyle='white';
context.stroke();
};
现在我们有了圆圈,让我们让它们移动,因为……
GIF
请记住,当我们创建圆时,我们使用了 arc 方法,它接受 x 和 y 坐标 - 圆的中心。 如果我们快速移动圆的 x 和 y 坐标,就会给人一种圆在移动的印象。 让我们试试吧!
//Define a speed by which to increment to the x and y coordinates
const dx=Math.random() * 3;
const dy=Math.random() * 7;//Increment the center of the circle with this speed
x=x + dx;
y=y - dy;
我们可以将其移至函数内 -
let x, y;
const move=()=> {
const dx=Math.random() * 3;
const dy=Math.random() * 7; x=x + dx;
y=y - dy;
};
为了让我们的圆圈无缝移动,我们将创建一个动画函数并使用浏览器的 requestAnimationFrame 方法来创建一个移动的圆圈。
const animate=()=> {
context.clearRect(0, 0, canvas.width, canvas.height);
move();
drawCircle(x,y); requestAnimationFrame(animate);
};//Don't forget to call animate at the bottom
animate();
现在我们已经创建了一个圆圈,是时候创建多个圆圈了!
但在我们创建多个圆圈之前,让我们准备一下我们的代码。为了避免重复我们的代码,我们将使用类并引入 Particle 类。 粒子是我们动态艺术作品和动画的构建块。 每个气泡都是一个粒子,具有自己的位置、大小、运动和颜色属性。 让我们定义一个 Particle 类来封装这些属性:
class Particle {
constructor(x=0, y=0) {}
draw() {
// Drawing the particle as a colored circle
// ...
} move() {
// Implementing particle movement
// ...
}
}
让我们将一些已设置的常量移至 Particle 类 -
class Particle {
constructor(x=0, y=0) {
this.x=x;
this.y=y;
this.radius=Math.random() * 50;
this.dx=Math.random() * 3;
this.dy=Math.random() * 7;
}
draw() {
// Drawing the particle as a colored circle
// ...
} move() {
// Implementing particle movement
// ...
}
}
draw 方法将负责在画布上渲染粒子。 我们已经在drawCircle中实现了这个功能,所以让我们将它移动到我们的类中并将变量更新为类变量
class Particle {
constructor(x=0, y=0) {
this.x=x;
this.y=y;
this.radius=Math.random() * 50;
this.dx=Math.random() * 3;
this.dy=Math.random() * 7;
this.color='white';
}
draw() {
context.beginPath();
context.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
context.strokeStyle=this.color;
context.stroke(); context.fillStyle=this.color;
context.fill();
} move() {}
}
同样,让我们在类中移动 move 函数 -
move() {
this.x=this.x + this.dx;
this.y=this.y - this.dy;
}
现在,我们需要确保在事件处理程序中调用 Particle 类。
const handleDrawCircle=(event)=> {
const x=event.pageX;
const y=event.pageY;
const particle=new Particle(x, y);
};canvas.addEventListener('click', handleDrawCircle);
由于我们需要在 animate 函数中访问该粒子,以便调用其 move 方法,因此我们将该粒子存储在一个名为 molecularArray 的数组中。 当创建大量粒子时,这个数组也会很有帮助。 这是反映这一点的更新代码 -
const particleArray=[];
const handleDrawCircle=(event)=> {
const x=event.pageX;
const y=event.pageY; const particle=new Particle(x, y);
particleArray.push(particle);
};canvas.addEventListener('click', handleDrawCircle);
记得也要更新动画功能 -
此时,您将在屏幕上看到这个粒子 -
惊人的! 现在,到了有趣的部分! 让我们创建很多圆圈并设计它们的样式,使它们看起来像气泡。
为了创建大量气泡,我们将使用 for 循环创建粒子并将它们添加到我们在此处创建的粒子数组中。
const handleDrawCircle=(event)=> {
const x=event.pageX;
const y=event.pageY;
for (let i=0; i < 50; i++) {
const particle=new Particle(x, y);
particleArray.push(particle);
}
};canvas.addEventListener('click', handleDrawCircle);
在动画函数中,我们将通过清除画布并在新位置重新绘制粒子来不断更新画布。 这会给人一种圆圈在移动的错觉。
const animate=()=> {
context.clearRect(0, 0, canvas.width, canvas.height);
particleArray.forEach((particle)=> {
particle?.move();
particle?.draw();
}); requestAnimationFrame(animate);
};animate();
现在我们有了移动的气泡,是时候给它们添加颜色,使它们看起来像气泡了!
我们将通过向气泡添加渐变填充来实现此目的。 这可以使用 context.createRadialGradient 方法来完成。
const gradient=context.createRadialGradient(
this.x,
this.y,
1,
this.x + 0.5,
this.y + 0.5,
this.radius
);
gradient.addColorStop(0.3, 'rgba(255, 255, 255, 0.3)');
gradient.addColorStop(0.95, '#e7feff');context.fillStyle=gradient;
恭喜! 您刚刚仅使用 HTML Canvas 和 JavaScript 创建了一些超级有趣的东西。 您已经学习了如何使用 arc 方法、利用 requestAnimationFrame、利用 JavaScript 类的强大功能以及使用渐变设计气泡以实现 3D 气泡效果。
请随意尝试颜色、速度和大小,使您的动画真正独一无二。
请随意尝试颜色、速度和大小,使您的动画真正独一无二。
我希望您在学习本教程时能像我在创建它时一样获得乐趣。 现在,轮到你进行实验了。 我很想看看你是否尝试过这个以及你创造了什么。 与我分享您的代码链接,我很乐意查看。
实现以下效果,分为几步:
画表心,表框有两个知识点:
//html
<canvas id="canvas" width="600" height="600"></canvas>
// js
// 设置中心点,此时300,300变成了坐标的0,0
ctx.translate(300, 300)
// 画圆线使用arc(中心点X,中心点Y,半径,起始角度,结束角度)
ctx.arc(0, 0, 100, 0, 2 * Math.PI)
ctx.arc(0, 0, 5, 0, 2 * Math.PI)
// 执行画线段的操作stroke
ctx.stroke()
复制代码
让我们来看看效果,发现了,好像不对啊,我们是想画两个独立的圆线,怎么画出来的两个圆连到一起了:
原因是:上面代码画连个圆时,是连着画的,所以画完大圆后,线还没斩断,就接着画小圆,那肯定会大圆小圆连一起,解决办法就是:beginPath,closePath
ctx.translate(300, 300) // 设置中心点,此时300,300变成了坐标的0,0
// 画大圆
+ ctx.beginPath()
// 画圆线使用arc(中心点X,中心点Y,半径,起始角度,结束角度)
ctx.arc(0, 0, 100, 0, 2 * Math.PI)
ctx.stroke() // 执行画线段的操作
+ ctx.closePath()
// 画小圆
+ ctx.beginPath()
ctx.arc(0, 0, 5, 0, 2 * Math.PI)
ctx.stroke()
+ ctx.closePath()
复制代码
画这三个指针,有两个知识点:
如何根据算好的角度去画线呢,比如算出当前是3点,那么时针就应该以12点为起始点,顺时针旋转2 * Math.PI / 12 * 3=90°,分针和秒针也是同样的道理,只不过跟时针不同的是比例问题而已,因为时在表上有12份,而分针和秒针都是60份
这时候又有一个新问题,还是以上面的例子为例,我算出了90°,那我们怎么画出时针呢?我们可以使用moveTo和lineTo去画线段。至于90°,我们只需要将x轴顺时针旋转90°,然后再画出这条线段,那就得到了指定角度的指针了。但是上面说了,是要以12点为起始点,我们的默认x轴确是水平的,所以我们时分秒针算出角度后,每次都要减去90°。可能这有点绕,我们通过下面的图演示一下,还是以上面3点的例子:
这样就得出了3点指针的画线角度了。
又又又有新问题了,比如现在我画完了时针,然后我想画分针,x轴已经在我画时针的时候偏转了,这时候肯定要让x轴恢复到原来的模样,我们才能继续画分针,否则画出来的分针是不准的。这时候save和restore就派上用场了,save是把ctx当前的状态打包压入栈中,restore是取出栈顶的状态并赋值给ctx,save可多次,但是restore取状态的次数必须等于save次数
懂得了上面所说,剩下画刻度了,起始刻度的道理跟时分秒针道理一样,只不过刻度是死的,不需要计算,只需要规则画出60个小刻度,和12个大刻度就行
const canvas=document.getElementById('canvas')
const ctx=canvas.getContext('2d')
ctx.translate(300, 300) // 设置中心点,此时300,300变成了坐标的0,0
// 把状态保存起来
+ ctx.save()
// 画大圆
ctx.beginPath()
// 画圆线使用arc(中心点X,中心点Y,半径,起始角度,结束角度)
ctx.arc(0, 0, 100, 0, 2 * Math.PI)
ctx.stroke() // 执行画线段的操作
ctx.closePath()
// 画小圆
ctx.beginPath()
ctx.arc(0, 0, 5, 0, 2 * Math.PI)
ctx.stroke()
ctx.closePath()
----- 新加代码 ------
// 获取当前 时,分,秒
let time=new Date()
let hour=time.getHours() % 12
let min=time.getMinutes()
let sec=time.getSeconds()
// 时针
ctx.rotate(2 * Math.PI / 12 * hour + 2 * Math.PI / 12 * (min / 60) - Math.PI / 2)
ctx.beginPath()
// moveTo设置画线起点
ctx.moveTo(-10, 0)
// lineTo设置画线经过点
ctx.lineTo(40, 0)
// 设置线宽
ctx.lineWidth=10
ctx.stroke()
ctx.closePath()
// 恢复成上一次save的状态
ctx.restore()
// 恢复完再保存一次
ctx.save()
// 分针
ctx.rotate(2 * Math.PI / 60 * min + 2 * Math.PI / 60 * (sec / 60) - Math.PI / 2)
ctx.beginPath()
ctx.moveTo(-10, 0)
ctx.lineTo(60, 0)
ctx.lineWidth=5
ctx.strokeStyle='blue'
ctx.stroke()
ctx.closePath()
ctx.restore()
ctx.save()
//秒针
ctx.rotate(2 * Math.PI / 60 * sec - - Math.PI / 2)
ctx.beginPath()
ctx.moveTo(-10, 0)
ctx.lineTo(80, 0)
ctx.strokeStyle='red'
ctx.stroke()
ctx.closePath()
ctx.restore()
ctx.save()
// 绘制刻度,也是跟绘制时分秒针一样,只不过刻度是死的
ctx.lineWidth=1
for (let i=0; i < 60; i++) {
ctx.rotate(2 * Math.PI / 60)
ctx.beginPath()
ctx.moveTo(90, 0)
ctx.lineTo(100, 0)
// ctx.strokeStyle='red'
ctx.stroke()
ctx.closePath()
}
ctx.restore()
ctx.save()
ctx.lineWidth=5
for (let i=0; i < 12; i++) {
ctx.rotate(2 * Math.PI / 12)
ctx.beginPath()
ctx.moveTo(85, 0)
ctx.lineTo(100, 0)
ctx.stroke()
ctx.closePath()
}
ctx.restore()
复制代码
最后一步就是更新视图,使时钟转动起来,第一想到的肯定是定时器setInterval,但是注意一个问题:每次更新视图的时候都要把上一次的画布清除,再开始画新的视图,不然就会出现千手观音的景象
附上最终代码:
const canvas=document.getElementById('canvas')
const ctx=canvas.getContext('2d')
setInterval(()=> {
ctx.save()
ctx.clearRect(0, 0, 600, 600)
ctx.translate(300, 300) // 设置中心点,此时300,300变成了坐标的0,0
ctx.save()
// 画大圆
ctx.beginPath()
// 画圆线使用arc(中心点X,中心点Y,半径,起始角度,结束角度)
ctx.arc(0, 0, 100, 0, 2 * Math.PI)
ctx.stroke() // 执行画线段的操作
ctx.closePath()
// 画小圆
ctx.beginPath()
ctx.arc(0, 0, 5, 0, 2 * Math.PI)
ctx.stroke()
ctx.closePath()
// 获取当前 时,分,秒
let time=new Date()
let hour=time.getHours() % 12
let min=time.getMinutes()
let sec=time.getSeconds()
// 时针
ctx.rotate(2 * Math.PI / 12 * hour + 2 * Math.PI / 12 * (min / 60) - Math.PI / 2)
ctx.beginPath()
// moveTo设置画线起点
ctx.moveTo(-10, 0)
// lineTo设置画线经过点
ctx.lineTo(40, 0)
// 设置线宽
ctx.lineWidth=10
ctx.stroke()
ctx.closePath()
ctx.restore()
ctx.save()
// 分针
ctx.rotate(2 * Math.PI / 60 * min + 2 * Math.PI / 60 * (sec / 60) - Math.PI / 2)
ctx.beginPath()
ctx.moveTo(-10, 0)
ctx.lineTo(60, 0)
ctx.lineWidth=5
ctx.strokeStyle='blue'
ctx.stroke()
ctx.closePath()
ctx.restore()
ctx.save()
//秒针
ctx.rotate(2 * Math.PI / 60 * sec - Math.PI / 2)
ctx.beginPath()
ctx.moveTo(-10, 0)
ctx.lineTo(80, 0)
ctx.strokeStyle='red'
ctx.stroke()
ctx.closePath()
ctx.restore()
ctx.save()
// 绘制刻度,也是跟绘制时分秒针一样,只不过刻度是死的
ctx.lineWidth=1
for (let i=0; i < 60; i++) {
ctx.rotate(2 * Math.PI / 60)
ctx.beginPath()
ctx.moveTo(90, 0)
ctx.lineTo(100, 0)
// ctx.strokeStyle='red'
ctx.stroke()
ctx.closePath()
}
ctx.restore()
ctx.save()
ctx.lineWidth=5
for (let i=0; i < 12; i++) {
ctx.rotate(2 * Math.PI / 12)
ctx.beginPath()
ctx.moveTo(85, 0)
ctx.lineTo(100, 0)
// ctx.strokeStyle='red'
ctx.stroke()
ctx.closePath()
}
ctx.restore()
ctx.restore()
}, 1000)
复制代码
效果 very good啊:
小时候很多人都买过充值卡把,懂的都懂啊哈,用指甲刮开这层灰皮,就能看底下的答案了。
思路是这样的:
关于fill这个方法,其实是对标stroke的,fill是把图形填充,stroke只是画出边框线
// html
<canvas id="canvas" width="400" height="100"></canvas>
<div class="text">恭喜您获得100w</div>
<style>
* {
margin: 0;
padding: 0;
}
.text {
position: absolute;
left: 130px;
top: 35px;
z-index: -1;
}
</style>
// js
const canvas=document.getElementById('canvas')
const ctx=canvas.getContext('2d')
// 填充的颜色
ctx.fillStyle='darkgray'
// 填充矩形 fillRect(起始X,起始Y,终点X,终点Y)
ctx.fillRect(0, 0, 400, 100)
ctx.fillStyle='#fff'
// 绘制填充文字
ctx.fillText('刮刮卡', 180, 50)
let isDraw=false
canvas.onmousedown=function () {
isDraw=true
}
canvas.onmousemove=function (e) {
if (!isDraw) return
// 计算鼠标在canvas里的位置
const x=e.pageX - canvas.offsetLeft
const y=e.pageY - canvas.offsetTop
// 设置globalCompositeOperation
ctx.globalCompositeOperation='destination-out'
// 画圆
ctx.arc(x, y, 10, 0, 2 * Math.PI)
// 填充圆形
ctx.fill()
}
canvas.onmouseup=function () {
isDraw=false
}
复制代码
效果如下:
框架:使用vue + elementUI
其实很简单,难点有以下几点:
第一点,只需要计算出鼠标点击的点坐标,以及鼠标的当前坐标,就可以计算了,矩形长宽计算:x - beginX, y - beginY,圆形则要利用勾股定理:Math.sqrt((x - beginX) * (x - beginX) + (y - beginY) * (y - beginY))
第二点,则要利用canvas的getImageData和putImageData方法
第三点,思路是将canvas生成图片链接,并赋值给具有下载功能的a标签,并主动点击a标签进行图片下载
看看效果吧:
具体代码我就不过多讲解了,说难也不难,只要前面两个项目理解了,这个项目很容易就懂了:
<template>
<div>
<div style="margin-bottom: 10px; display: flex; align-items: center">
<el-button @click="changeType('huabi')" type="primary">画笔</el-button>
<el-button @click="changeType('rect')" type="success">正方形</el-button>
<el-button
@click="changeType('arc')"
type="warning"
style="margin-right: 10px"
>圆形</el-button
>
<div>颜色:</div>
<el-color-picker v-model="color"></el-color-picker>
<el-button @click="clear">清空</el-button>
<el-button @click="saveImg">保存</el-button>
</div>
<canvas
id="canvas"
width="800"
height="400"
@mousedown="canvasDown"
@mousemove="canvasMove"
@mouseout="canvasUp"
@mouseup="canvasUp"
>
</canvas>
</div>
</template>
<script>
export default {
data() {
return {
type: "huabi",
isDraw: false,
canvasDom: null,
ctx: null,
beginX: 0,
beginY: 0,
color: "#000",
imageData: null,
};
},
mounted() {
this.canvasDom=document.getElementById("canvas");
this.ctx=this.canvasDom.getContext("2d");
},
methods: {
changeType(type) {
this.type=type;
},
canvasDown(e) {
this.isDraw=true;
const canvas=this.canvasDom;
this.beginX=e.pageX - canvas.offsetLeft;
this.beginY=e.pageY - canvas.offsetTop;
},
canvasMove(e) {
if (!this.isDraw) return;
const canvas=this.canvasDom;
const ctx=this.ctx;
const x=e.pageX - canvas.offsetLeft;
const y=e.pageY - canvas.offsetTop;
this[`${this.type}Fn`](ctx, x, y);
},
canvasUp() {
this.imageData=this.ctx.getImageData(0, 0, 800, 400);
this.isDraw=false;
},
huabiFn(ctx, x, y) {
ctx.beginPath();
ctx.arc(x, y, 5, 0, 2 * Math.PI);
ctx.fillStyle=this.color;
ctx.fill();
ctx.closePath();
},
rectFn(ctx, x, y) {
const beginX=this.beginX;
const beginY=this.beginY;
ctx.clearRect(0, 0, 800, 400);
this.imageData && ctx.putImageData(this.imageData, 0, 0, 0, 0, 800, 400);
ctx.beginPath();
ctx.strokeStyle=this.color;
ctx.rect(beginX, beginY, x - beginX, y - beginY);
ctx.stroke();
ctx.closePath();
},
arcFn(ctx, x, y) {
const beginX=this.beginX;
const beginY=this.beginY;
this.isDraw && ctx.clearRect(0, 0, 800, 400);
this.imageData && ctx.putImageData(this.imageData, 0, 0, 0, 0, 800, 400);
ctx.beginPath();
ctx.strokeStyle=this.color;
ctx.arc(
beginX,
beginY,
Math.round(
Math.sqrt((x - beginX) * (x - beginX) + (y - beginY) * (y - beginY))
),
0,
2 * Math.PI
);
ctx.stroke();
ctx.closePath();
},
saveImg() {
const url=this.canvasDom.toDataURL();
const a=document.createElement("a");
a.download="sunshine";
a.href=url;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
},
clear() {
this.imageData=null
this.ctx.clearRect(0, 0, 800, 400)
}
},
};
</script>
<style lang="scss" scoped>
#canvas {
border: 1px solid black;
}
</style>
复制代码
公众号:小何成长,佛系更文,都是自己曾经踩过的坑或者是学到的东西
有兴趣的小伙伴欢迎关注我哦,我是:何小玍。 大家一起进步鸭
*请认真填写需求信息,我们会在24小时内与您取得联系。