Canvas
示例一
html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
canvas {
position: fixed;
left: 0;
top: 0;
background: #222;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<canvas></canvas>
<script src="./index.js"></script>
</body>
</html>js
const cvs = document.querySelector('canvas')
const ctx = cvs.getContext('2d')
function init() {
cvs.width = window.innerWidth * window.devicePixelRatio
cvs.height = window.innerHeight * window.devicePixelRatio
}
init()
/**
* 随机数
* @param {*} min 最小值
* @param {*} max 最大值
* @returns 随机数
*/
function getRandom(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min)
}
class Point {
constructor() {
this.r = 6
this.x = getRandom(0, cvs.width - this.r / 2)
this.y = getRandom(0, cvs.height - this.r / 2)
this.xSpeed = getRandom(-50, 50)
this.ySpeed = getRandom(-50, 50)
this.lastDrawTime = null
}
draw() {
// 更新坐标
if (this.lastDrawTime) {
// 计算新的坐标
const duration = (Date.now() - this.lastDrawTime) / 1000
const xDis = this.xSpeed * duration
const yDis = this.ySpeed * duration
const x = this.x + xDis
const y = this.y + yDis
// 处理边界反弹
if (x > cvs.width - this.r / 2) {
this.xSpeed = -this.xSpeed
}
if (x < this.r / 2) {
this.xSpeed = -this.xSpeed
}
if (y > cvs.height - this.r / 2) {
this.ySpeed = -this.ySpeed
}
if (y < this.r / 2) {
this.ySpeed = -this.ySpeed
}
this.x = x
this.y = y
}
ctx.beginPath()
// 画小圆圈
ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2)
ctx.fillStyle = 'rgba(200, 200, 200, 1)'
ctx.fill()
// 记录当前绘制时间
this.lastDrawTime = Date.now()
}
}
class Graph {
constructor(pointNumber = 30, maxDistance = 500) {
this.maxDistance = maxDistance
this.points = new Array(pointNumber).fill(0).map(() => new Point())
}
draw() {
requestAnimationFrame(() => {
this.draw()
})
// 清除前一帧画布画布
ctx.clearRect(0, 0, cvs.width, cvs.height)
for (let i = 0; i < this.points.length; i++) {
const p1 = this.points[i]
p1.draw()
for (let j = i + 1; j < this.points.length; j++) {
const p2 = this.points[j]
const distance = Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2)
if (distance > this.maxDistance) continue
ctx.beginPath()
ctx.moveTo(p1.x, p1.y)
ctx.lineTo(p2.x, p2.y)
ctx.closePath()
// 根据两点之间的距离处理清晰度
ctx.strokeStyle = `rgba(200, 200, 200, ${1 - distance / this.maxDistance})`
ctx.stroke()
}
}
}
}
const g = new Graph()
g.draw()示例二
文字
html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
canvas {
display: block;
width: 100%;
height: 100%;
background: #000;
}
</style>
</head>
<body>
<canvas></canvas>
<script src="./index.js"></script>
</body>
</html>js
const cvs = document.querySelector('canvas')
const ctx = cvs.getContext('2d')
function init() {
cvs.width = window.innerWidth * devicePixelRatio
cvs.height = window.innerHeight * devicePixelRatio
}
init()
const fontSize = 10 * devicePixelRatio
ctx.font = `${fontSize}px "JetBrains Mono"`
// 一屏能放多少列
const columnCount = Math.floor(cvs.width / fontSize)
// 每一列当前画到第几行
const charIndex = new Array(columnCount).fill(0)
/**
* 每一帧怎么画
*/
function draw() {
// 用很淡的黑色覆盖全屏 不是完全清空 所以后续字符会慢慢变淡 形成拖影
ctx.fillStyle = 'rgba(0,0,0,0.1)'
ctx.fillRect(0, 0, cvs.width, cvs.height)
ctx.fillStyle = '#6be445'
ctx.textBaseline = 'top'
for (let i = 0; i < columnCount; i++) {
const text = getRandomChar()
// 列位置
const x = i * fontSize
// 该列当前下落到的 y
const y = charIndex[i] * fontSize
// 画字符
ctx.fillText(text, x, y)
// 下移一行
charIndex[i]++
// 跑出屏幕 重新从顶部开始 (设置概率 让每列重置时间不一致)
if (y >= cvs.height && Math.random() > 0.99) {
charIndex[i] = 0
}
}
}
ctx.fillStyle = '#fff'
ctx.fillText('hello world', 300, 400)
function getRandomChar() {
return String.fromCharCode(Math.floor(Math.random() * 128))
}
setInterval(draw, 50)示例三
图片
html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<canvas></canvas>
<script src="./index.js"></script>
</body>
</html>js
const cvs = document.querySelector('canvas')
const ctx = cvs.getContext('2d', {
willReadFrequently: true
})
function init() {
const img = new Image()
img.onload = () => {
cvs.width = img.width
cvs.height = img.height
ctx.drawImage(img, 0, 0)
}
img.src = './na.jpg'
}
init()
cvs.addEventListener('click', (e) => {
const x = e.offsetX
const y = e.offsetY
// console.log(x, y)
// 拿到整张图片的每个像素点的数据
const imgData = ctx.getImageData(0, 0, cvs.width, cvs.height)
const clickColor = getColor(x, y, imgData)
const greenColor = [0, 255, 0, 255]
function _changeColor(x, y) {
if (x < 0 || x >= cvs.width || y < 0 || y >= cvs.height) return
const i = point2Index(x, y)
const color = getColor(x, y, imgData)
if (diffColor(color, clickColor) > 100) return
if (imgData.data[i] === 0) return
// 第二个参数是偏移量
imgData.data.set(greenColor, i)
_changeColor(x + 1, y)
_changeColor(x, y + 1)
_changeColor(x - 1, y)
_changeColor(x, y - 1)
}
_changeColor(x, y)
// 把 imgData 绘制到 canvas 上
ctx.putImageData(imgData, 0, 0)
// console.log(clickColor)
})
function point2Index(x, y) {
return (y * cvs.width + x) * 4
}
function getColor(x, y, imgData) {
const i = point2Index(x, y)
return [
imgData.data[i],
imgData.data[i + 1],
imgData.data[i + 2],
imgData.data[i + 3]
]
}
function diffColor(color1, color2) {
return (
Math.abs(color1[0] - color2[0]) +
Math.abs(color1[1] - color2[1]) +
Math.abs(color1[2] - color2[2]) +
Math.abs(color1[3] - color2[3])
)
}示例四
绘制矩形 能拖动
html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<input type="color" name="" id="" />
<canvas></canvas>
<script src="./index.js"></script>
</body>
</html>js
const colorPicker = document.querySelector('input')
const cvs = document.querySelector('canvas')
const ctx = cvs.getContext('2d')
function init() {
const w = 500
const h = 300
cvs.width = w * devicePixelRatio
cvs.height = h * devicePixelRatio
cvs.style.width = w + 'px'
cvs.style.height = h + 'px'
cvs.style.backgroundColor = '#978e8eff'
}
init()
const shapes = []
class Rectangle {
constructor(color, startX, startY) {
this.color = color
this.startX = startX
this.startY = startY
this.endX = startX
this.endY = startY
}
get minX() {
return Math.min(this.startX, this.endX)
}
get maxX() {
return Math.max(this.startX, this.endX)
}
get minY() {
return Math.min(this.startY, this.endY)
}
get maxY() {
return Math.max(this.startY, this.endY)
}
draw() {
ctx.beginPath()
ctx.moveTo(this.maxX * devicePixelRatio, this.minY * devicePixelRatio)
ctx.lineTo(this.maxX * devicePixelRatio, this.maxY * devicePixelRatio)
ctx.lineTo(this.minX * devicePixelRatio, this.maxY * devicePixelRatio)
ctx.lineTo(this.minX * devicePixelRatio, this.minY * devicePixelRatio)
ctx.closePath()
ctx.fillStyle = this.color
ctx.fill()
ctx.strokeStyle = '#fff'
ctx.lineCap = 'square'
ctx.lineWidth = 3 * devicePixelRatio
ctx.stroke()
}
/**
* 判断是否在矩形内
* @param {*} x 点的x坐标
* @param {*} y 点的y坐标
* @returns 是否在矩形内
*/
isInside(x, y) {
return x >= this.minX && x <= this.maxX && y >= this.minY && y <= this.maxY
}
}
const rect = new Rectangle(colorPicker.value, 100, 100)
// rect.endX = 200
// rect.endY = 200
// rect.draw()
// 鼠标按下
cvs.onmousedown = (e) => {
// 获取canvas的坐标
const rect = cvs.getBoundingClientRect()
// 记录点击时的坐标 后续用鼠标移动距离并计算拖动的距离
const clickX = e.clientX - rect.left
const clickY = e.clientY - rect.top
// 判断是否在矩形内
const shape = getShape(clickX, clickY)
if (shape) {
// 拖动
const { startX, startY, endX, endY } = shape
window.onmousemove = (e) => {
// 计算拖动的距离
const disX = e.clientX - rect.left - clickX
const disY = e.clientY - rect.top - clickY
shape.startX = startX + disX
shape.startY = startY + disY
shape.endX = endX + disX
shape.endY = endY + disY
}
} else {
// 新建
const newRect = new Rectangle(colorPicker.value, clickX, clickY)
shapes.push(newRect)
window.onmousemove = (e) => {
// 指定矩阵结束坐标为鼠标松开时的坐标
newRect.endX = e.clientX - rect.left
newRect.endY = e.clientY - rect.top
newRect.draw()
}
}
// 鼠标松开 接触绑定事件
window.onmouseup = () => {
window.onmouseup = null
window.onmousemove = null
}
}
/**
* 获取点击的矩形对象
* @param {Number} x 点的x坐标
* @param {Number} y 点的y坐标
* @returns 矩形对象
*/
function getShape(x, y) {
// 从后往前遍历,先绘制的矩形在后面,所以先判断后面的矩形是否在点击点内
for (let i = shapes.length - 1; i >= 0; i--) {
const s = shapes[i]
if (s.isInside(x, y)) {
return s
}
}
return null
}
function draw() {
requestAnimationFrame(draw)
ctx.clearRect(0, 0, cvs.width, cvs.height)
// 可自行设置状态值 来判断是否需要重新绘制
shapes.forEach((shape) => shape.draw())
}
draw()