共计 21190 个字符,预计需要花费 53 分钟才能阅读完成。
导航
[[深刻 01] 执行上下文](https://juejin.im/post/684490…
[[深刻 02] 原型链](https://juejin.im/post/684490…
[[深刻 03] 继承](https://juejin.im/post/684490…
[[深刻 04] 事件循环](https://juejin.im/post/684490…
[[深刻 05] 柯里化 偏函数 函数记忆](https://juejin.im/post/684490…
[[深刻 06] 隐式转换 和 运算符](https://juejin.im/post/684490…
[[深刻 07] 浏览器缓存机制(http 缓存机制)](https://juejin.im/post/684490…
[[深刻 08] 前端平安](https://juejin.im/post/684490…
[[深刻 09] 深浅拷贝](https://juejin.im/post/684490…
[[深刻 10] Debounce Throttle](https://juejin.im/post/684490…
[[深刻 11] 前端路由](https://juejin.im/post/684490…
[[深刻 12] 前端模块化](https://juejin.im/post/684490…
[[深刻 13] 观察者模式 公布订阅模式 双向数据绑定](https://juejin.im/post/684490…
[[深刻 14] canvas](https://juejin.im/post/684490…
[[深刻 15] webSocket](https://juejin.im/post/684490…
[[深刻 16] webpack](https://juejin.im/post/684490…
[[深刻 17] http 和 https](https://juejin.im/post/684490…
[[深刻 18] CSS-interview](https://juejin.im/post/684490…
[[深刻 19] 手写 Promise](https://juejin.im/post/684490…
[[深刻 20] 手写函数](https://juejin.im/post/684490…
[[react] Hooks](https://juejin.im/post/684490…
[[部署 01] Nginx](https://juejin.im/post/684490…
[[部署 02] Docker 部署 vue 我的项目](https://juejin.im/post/684490…
[[部署 03] gitlab-CI](https://juejin.im/post/684490…
[[源码 -webpack01- 前置常识] AST 形象语法树](https://juejin.im/post/684490…
[[源码 -webpack02- 前置常识] Tapable](https://juejin.im/post/684490…
[[源码 -webpack03] 手写 webpack – compiler 简略编译流程](https://juejin.im/post/684490…
[[源码] Redux React-Redux01](https://juejin.im/post/684490…
[[源码] axios ](https://juejin.im/post/684490…
[[源码] vuex ](https://juejin.im/post/684490…
[[源码 -vue01] data 响应式 和 初始化渲染 ](https://juejin.im/post/684490…
[[源码 -vue02] computed 响应式 – 初始化,拜访,更新过程 ](https://juejin.im/post/684490…
前置常识
一些单词
canvas:画布 | |
triangle:三角形 | |
rectangle:矩形 | |
arc:弧 | |
anti:拥护,反 | |
clockwise:顺时针方向 | |
anticlockwise:逆时针方向 | |
curve:曲线 | |
quadratic:平方的 |
弧长 弧度
-
<font color=red> 弧度 = 弧长 / 半径 </font>
弧度 = 弧长 / 半径 圆的弧长 = 2PI * R // 即周长 1°的弧长 = 2PI * R / 360 = PI * R / 180 1°的弧度 = PI / 180
canvas
属性
-
只有两个属性:with 和 height
默认的 width=300,height=150
<canvas id="canvas" width="200" height="200" style="border: 1px solid red;" ></canvas> - 规范形式:canvas 标签自带的 width 和 height 属性
- css 形式
-
js 形式:domTarget.width 和 domTarget.height
在不反对 canvas 的浏览器中显示 (替换内容)
- 替换内容:写在 canvas 便签内
- 不反对的浏览器将显示替换内容,而反对的浏览器会疏忽标签内的内容
-
留神:<font color=red>canvas 必须有完结标签,如果没有,前面的内容将被认为是替换的内容 </font>
<canvas id="canvas" width="200" height="200"> 替换的内容 // <img src="images/clock.png" width="150" height="150" alt=""/> </canvas>
渲染上下文 – the render context
-
<font color=red>getContext() 办法 </font>
- 获取 (<font color=red> 渲染上下文 </font>) 和 (<font color=red> 绘画性能 </font>)
- 参数:示意(<font color=red> 上下文的格局 </font>)
2d
,3d
如何判断浏览器是否反对 canvas 标签
var canvas = document.getElementById('canvas'); | |
if (canvas.getContext) { // ------------------------ 通过判断 getContext 办法是否存在来判断 | |
console.log('你的浏览器反对 Canvas!'); | |
} else {console.log('你的浏览器不反对 Canvas!'); | |
} |
模块
<canvas | |
id="canvas" width="200" height="200" | |
style="border: 1px solid red;" | |
> | |
替换的内容 | |
</canvas> | |
<script> | |
window.addEventListener('load', draw, false) | |
// load 事件:在页面加载实现时候触发,包含 DOM,图片,视频等所有资源加载结束时执行 | |
// DOMContentLoaded:在 DOM 加载实现时触发 | |
// 或者 <body onload="draw();">...</body> | |
function draw() {var canvas = document.getElementById('canvas'); | |
if (canvas.getContext) { // ------------------------ getCotext 办法存在,阐明浏览器反对 canvas | |
console.log('你的浏览器反对 Canvas!'); | |
var ctx = canvas.getContext('2d'); | |
// 实现绘画的逻辑... | |
} else {console.log('你的浏览器不反对 Canvas!'); | |
} | |
} |
绘制的过程
- <font color=red>先定义状态,后绘制</font>
绘制 <font color=red> 矩形 </font> rectangle:矩形
- fillRect(x, y, width, height) ——– 填充矩形,
x y 示意矩形左上角的坐标,原点是左上角 0 0 地位
- strokeRect(x, y, width, height) —- 边框矩形
-
clearRect(x, y, width, height) —— 革除矩形区域,使其革除局部齐全通明
// 矩形 ctx.fillRect(300, 100, 100, 100) // 填充矩形 ctx.clearRect(350, 150, 30, 30); // 革除矩形区域,使其革除局部齐全通明 ctx.strokeRect(400, 200, 50, 50) // 矩形边框
画 一条 <font color=red> 直线 </font> 和 一个 <font color=red> 三角形 </font>
- beginPath() ———- 新建门路,新建后能够绘制
- closePath() ———– 闭合门路,闭合后能够从新绘制,( <font color=red> 非必须,从新 beginPath 也行 </font>)
- <font color=red>stroke() ————— 用线条绘制 </font>
- <font color=red>fill() ——————- 填充绘制 </font>
- moveTo() ————- 起始地位
- lineTo() ————— 绘制直线,是一个点
- <font color=red>lineWidth:———– 直线的宽度,是一个数字 </font>
- <font color=red>lineCap:————- 直线的末端线帽款式,’round'</font>
- save()—————- 保留整个环境
-
restore() ———— 复原之前的环境,如旋转 canvas 后,须要复原之前的环境,不然所有后续的绘图都会旋转
function drawLineAndTriangle(ctx) { // 三角形 ctx.beginPath() //------------------------------------------ 一个门路的开始 ctx.moveTo(100, 100) // ----- 起始点 ctx.lineTo(80, 120) // ------ 直线的第二个点 ctx.lineTo(120, 120) // ----- 直线的第三个点 ctx.closePath() // ----------------------------------------- 一个门路的完结 ctx.lineWidth = 4 // -------- 直线的宽度 ctx.strokeStyle = 'red' // ------ 直线的色彩,须要在绘画前设置 ctx.stroke() // --------------------------------------------- 描边 (绘制) ctx.fillStyle= 'yellow' // ------ 填充的色彩,须要在绘画前设置 ctx.fill() // ----------------------------------------------- 填充 (绘制) // 直线 ctx.beginPath() ctx.moveTo(30, 30) ctx.lineTo(100, 30) ctx.closePath() ctx.lineWidth = 2!!!!!!!!!!!!!!!!!!!ctx.strokeStyle = 'blue' ctx.stroke()}
画 <font color=red> 弧线 </font> 和 <font color=red> 圆 </font>
-
arc(x, y, radius, startAngle, endAngle, anticlockwise)
以 x,y 为圆心,radius 为半径,startAngle 和 endAngle 为角度,anticlockwise 为方向的圆弧(圆)
- anticlockwise:布尔值,示意是否逆时针方向,
默认的方向是顺时针
- startAngle, endAngle 代表的是(<font color=red> 弧度 </font>),而不是角度
- 留神:<font color=red> 起始角度为三点钟地位,并且是以弧度计算的 </font>
弧度 =(Math.PI/180)* 角度
-
arcTo(x1, y1, x2, y2, radius)
依据给定的控制点和半径画一段圆弧,再以直线连贯两个控制点。
-
arc:是弧的意思
// 圆弧 ctx.beginPath() ctx.arc(200, 150, 40, 90 * Math.PI/180, 1.5 * Math.PI, false) ctx.stroke() // 圆 ctx.beginPath() ctx.arc(200, 350, 40, 0, 2 * Math.PI, false) ctx.fill()
画一个笑脸
function drawLineAndTriangle(ctx) {ctx.beginPath() | |
ctx.arc(300, 300, 200, 0, 2 * Math.PI) // --------------------- 大圆 | |
ctx.stroke() | |
// ctx.closePath() 可要可不要 | |
ctx.beginPath() | |
ctx.arc(250, 200, 6, 0, 2* Math.PI) // ------------------------ 左眼 | |
ctx.stroke() | |
ctx.beginPath() | |
ctx.arc(350, 200, 6, 0, 2* Math.PI) // ------------------------ 右眼 | |
ctx.stroke() | |
ctx.beginPath() | |
ctx.arc(300, 300, 150, 0, 1 * Math.PI) ------------------------- 嘴 | |
ctx.stroke()} |
二次贝塞尔曲线,三次贝塞尔曲线
-
<font color=red>quadraticCurveTo(cp1x, cp1y, x, y)</font> —————— 绘制二次贝塞尔曲线
- cp1x,cp1y 为一个控制点,x,y 为完结点
-
<font color=red>bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)</font> ——— 绘制三次贝塞尔曲线
- cp1x,cp1y 为控制点一
- cp2x,cp2y 为控制点二
- x,y 为完结点
- 留神:起始点通过 moveTo()获取
color
- fillStyle
- strokeStyle
globalAlpha – 透明度
- globalAlpha:设置透明度
0- 1 之间
- 留神:<font color=red>globalAlpha 和 fillStyle 和 strokeStyle 须要在绘画前设置 </font>
lineCap:线段起点的样子
- butt
- round:圆头
-
square
ctx.beginPath(); ctx.moveTo(100, 100) ctx.lineTo(100, 300) ctx.strokeStyle = 'red' ctx.lineWidth = 20 ctx.lineCap = 'round' // ------------------ 设置线段起点的样子为圆形 // 留神:所有的状态设置都必须在 stroke 绘画后面 ctx.stroke()
<font color=red> 实例 1:canvas 实现生成一张图片保留到本地 </font>
drawImage
- context.drawImage(img,x,y)
- <font color=red>context.drawImage(img, x, y, width, height)</font>
- context.drawImage(img,sx,sy,swidth,sheight,x,y,width,height)
img:图片
x:在画布上搁置图像的 x 坐标
y:在画布上搁置图像的 y 坐标
witdh:图像的宽度
height:图像的高度
- sx:剪切图像的 x 坐标
- sy:剪切图像的 y 坐标
- swidth:剪切图像的宽度
- sheight:剪切图像的高度
- <font color=red> 留神:剪切局部是去掉的局部,不显示 </font>
fillText 和 font
-
<font color=red>context.fillText(text,x,y,maxWidth)</font> 在画布上绘制填色的文本
- tetx 文本,xy 坐标,maxWidth 容许的最大文本宽度
context.font = "" 设置或返回字体属性
canvas.toDataURL()
- <font color=red>canvas.toDataURL(type, encoderOptions):返回一个蕴含图片展现的 URI</font>
- type:图片的类型
image/png
- encoderOptions:图片品质
- 返回值:一个蕴含图片展现的 URI,默认为 PNG 格局,图片的分辨率为 96dpi
new Image()
new Image(width, height) 用于生成 HTMLImageElement 实例
- 留神:用 js 生成的 img 实例,并不在文档中,须要手动插入
- 属性:src,currentSrc
-
办法:
- onload:图像加载实现,会触发 onload 属性指定的回调函数
- onerror:图像加载实现,同时也会触发 onerror 属性指定的回调函数
new Image(width, heght) mounted() {const limg = require('../images/1.jpg'); const img = new Image(200, 200); ------------- 参数别离是 width 和 height img.src = limg; ---------------- 除了 src,还有 currentSrc 示意以后 src,因为 src 能够动静指定 img.onload = function() {console.log('加载实现'); document.body.appendChild(img); -------------- 插入文档 } img.onerror = function() {console.log('谬误') } }
------ | |
canvas 实现生成图片保留到本地 | |
实例:<!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> | |
#imgContainer { | |
width: 400px; | |
height: 400px; | |
border: 1px solid black; | |
} | |
</style> | |
</head> | |
<body> | |
<div> | |
<img src="./002.jpg" alt="002.jpg" id="imgx"> | |
<button id="button"> 生成图片 </button> | |
<div id="imgContainer"></div> | |
</div> | |
<script> | |
window.onload = function() {const imgx = document.getElementById('imgx') | |
const button = document.getElementById('button') | |
const imgContainer = document.getElementById('imgContainer') | |
button.addEventListener('click', clickButton, false) | |
function clickButton() {combine() | |
} | |
function combine() {const canvas = document.createElement('canvas') // -------- 创立 canvas 标签 | |
canvas.width = 500 | |
canvas.height = 500 | |
canvas.style = "border: 1px solid red" | |
document.documentElement.appendChild(canvas) // ------------ 增加到 HTML 的 DOM 中 | |
const context = canvas.getContext('2d') // ----------------- 获取渲染上下文和绘画性能 | |
context.drawImage(imgx, 0, 0, 300, 300) // ----------------- drawImage() 生成图片 | |
context.fillStyle = 'white'; | |
context.font = '30px Georgia'; | |
context.fillText('生成的图片', 60, 60) // ------------------- 填充文字 | |
const currentUrl = canvas.toDataURL('image/png') // -------- toDataURL() 返回图片的 URI | |
imgContainer.innerHTML = `<img src=${currentUrl}>` // ------ 填充内容 | |
} | |
} | |
</script> | |
</body> | |
</html> |
<font color=red> 实例 2:实现一个粒子文字动画 </font>
getImageData()
context.getImageData(x,y,width,height)
- getImageData() 返回 ( ImageData 对象),获取画布指定矩形的 <font color=red> 像素数据 </font>
putImageData()
- 将 ImageData 对象 绘制到 canvas 上
ImageData 对象
- 蕴含
width
height
data
三个属性 -
<font color=red>data 属性:是一个数组(Uint8ClampedArray),蕴含以 RGBA 程序的数据,数据应用 0 至 255(蕴含)的整数示意 </font>
- 图像是二维的,由 height 决定行数,width 决定列数
- R – 红色 (0-255)
- G – 绿色 (0-255)
- B – 蓝色 (0-255)
- A – alpha 通道 (0-255; 0 是通明的,255 是齐全可见的)
- <font color=red>alpha 的值大于 128,即为有色彩的点 </font>
- <font color=red> 留神:data 中点的排列程序是 从左到右,从上到下的程序,而每个点占数组的四个成员</font>
context.save() 和 context.restore()
-
context.save()用来保留 canvas 的状态
- save 后能够调用 canvas 的平移,缩放,旋转,裁剪等操作
-
context.restore()
- 复原之前保留的状态
实现动态粒子文字
<!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> | |
<script> | |
class CanvasPartiular {constructor() { | |
this.clientWidth = null // html 宽度 | |
this.clientHeight = null // html 高度 | |
this.sx = null // canvas 的 drawImage 画图的 x 坐标,同时也是图片的宽度 | |
this.sy = null // canvas 的 drawImage 画图的 y 坐标,同时也是图片的高度 | |
this.canvas = null // canvas 实例 | |
this.context = null // canvas 的渲染上下文 | |
this.img = null // new Image() 生成的图片 | |
this.imageData = null // iamgeData 对象,包含 width,height,data 数组 | |
this.dotArr = [] // ------------------------------ 用来收集像素点,成员是一个蕴含 x,y, cricle 的对象 | |
this.createCanvas() // 创立函数 | |
this.createImage() // 创立函数} | |
createCanvas = () => { | |
const HTML = document.documentElement | |
const clientWidth = HTML.clientWidth | |
const clientHeight = HTML.clientHeight | |
const canvas = this.canvas = document.createElement('canvas') // ------------- 创立 canvas | |
canvas.width = this.clientWidth = clientWidth | |
canvas.height = this.clientHeight = clientHeight | |
canvas.style.border = '1px solid black'; | |
this.context = canvas.getContext('2d') // ------------------------------------ 获取 context | |
document.body.appendChild(canvas) | |
} | |
createImage = () => {const img = this.img = new Image() | |
img.src = './5.jpg' | |
if (img.complete) { // -------------------------- if else 保障了图片加载实现后再执行 init 办法 | |
this.init()} | |
else {img.onload = this.init} | |
} | |
init = () => { | |
const sx = this.sx = this.clientWidth/2 - this.img.width/2; | |
// 横坐标和宽,能够本人用两个正方形验证 | |
const sy = this.sy = this.clientHeight/2 - this.img.height/2; | |
this.context.drawImage(this.img, sx, sy) | |
// 画图 | |
const imageData = this.imageData = this.context.getImageData(sx, sy, this.img.width, this.img.height) | |
// 获取 imageData 对象 | |
this.getDotList()} | |
// 重点是该函数,获取 dotList 数组 | |
getDotList = () => { | |
const dataArr = this.imageData.data | |
const imgWidth = this.imageData.width | |
for(let x = 0; x < imgWidth; x = x + 6) { | |
// x 示意横轴的点,每次减少 6 则每个点之间有间隙 | |
for(let y = 0; y < this.imageData.height; y = y + 6) { | |
// y 示意纵轴 | |
const iDotPositionInArray = (y * imgWidth + x) * 4 | |
// (1) y * imgWidth:示意该点的地位曾经是第 y 行了,即有 y * imgWidth 个点 | |
// (2) y * imgWidth + x:示意该点的具体位置,即后面有 y * imgWidth + x 个点 | |
// (3) (y * imgWidth + x) * 4:示意再 data 数组中,该点的地位。因为每个点占据 data 数组的 4 个成员 | |
if(dataArr[iDotPositionInArray + 3] > 256/2 && dataArr[iDotPositionInArray] < 100) { | |
// iDotPositionInArray + 3:示意该点的 Alpha 透明度 | |
// Alpha 在 0 - 256 之间 | |
// 256/2:示意该点可见,不是通明的 | |
this.dotArr.push({x, y, radius: 2}) // x,y 示意坐标,radius 半径,半径轻易设适合即可 | |
} | |
} | |
} | |
this.draw()} | |
draw = () => { | |
const context = this.context | |
// context.clearRect(0, 0, this.clientWidth, this.clientHeight); | |
// clearRect 革除矩形的 canvas,即革除 drawImage 的图片,上面从新画点图 | |
context.fillStyle = 'black' | |
this.dotArr.forEach(({x,y,radius}) => {context.save() | |
context.beginPath() | |
context.arc(x, y, radius, 0, 360 * Math.PI/180) // 画圆 | |
context.fill() | |
context.restore()}) | |
} | |
} | |
new CanvasPartiular() | |
</script> | |
</body> | |
</html> |
实例 3:时钟动画
- 弧度 = 弧长 / 半径
context.translate()
- <font color=red>context.translate(x, y),从新映射画布上的 (0, 0) 地位 </font>
context.font
- <font color=red>context.font = “40px Arial” 设置文本内容的字体属性 </font>
context.textAlign
- <font color=red>context.textAlign = ‘center’ ——— 设置文本 (左右对齐) 形式 </font>
context.textBaseline
- <font color=red>context.textBaseline = ‘middle’ —– 设置文本的 (高低对齐) 形式 </font>
context.fillText()
- <font color=red>context.fillText(text, x, y, maxWidth) 在画布上绘制填色的文本 </font>,默认彩色
sin 函数,cos 函数
cos(180° - a) = - cos(a)
sin(180° - a) = sin(a)
sin(2a) = 2 * sin(a) * cos(a)
context.rotate()
- <font color=red>context.rotate(angle) 旋转以后绘图,参数以弧度计算 </font>
context.lineWidth
- <font color=red>context.lineWidth = 10 设置线条宽度,以像素计算 </font>
context.lineCap
- <font color=red>context.lineCap = ’round’ 设置线条末端线帽的款式 </font>
context.save() 和 context.restore()
- context.save() 保留以后环境状态
- context.restore() 返回之前保留过的门路状态和属性
<!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> | |
* { | |
margin: 0; | |
padding: 0; | |
} | |
html {height: 100%;} | |
body { | |
height: 100%; | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
} | |
#canvas {border: 1px solid red;} | |
</style> | |
</head> | |
<body> | |
<canvas id="canvas" width=600 height=600></canvas> | |
<script> | |
const canvas = document.getElementById('canvas') | |
const cWidth = canvas.width | |
const cHeight = canvas.height | |
const context = canvas.getContext('2d') | |
const center = context.translate(cWidth/2, cHeight/2) | |
const arcRadius = cWidth/2; | |
const rate = cWidth / 600; // -------- 比例,以以后大小 600 为基准,如果 cWidth = 1200 则 rate 为 2,放大一倍 | |
function clockBorder() { // 时钟外圆边框 | |
context.beginPath() | |
context.arc(0, 0, (arcRadius - 20/2) * rate, 0, 360 * Math.PI/180) // 时钟大圆 | |
context.strokeStyle='blue' | |
context.lineWidth = 20 * rate; | |
context.stroke() | |
context.closePath()} | |
function clockNumber() { // 数字刻度 | |
var numberArr = [3,4,5,6,7,8,9,10,11,12,1,2]; | |
context.font = `${30 * rate}px Arail`; | |
context.textAlign = 'center'; | |
context.textBaseline = 'middle'; | |
context.fillStyle = 'black'; | |
numberArr.forEach((item, index) => { | |
const rad = 2 * Math.PI / 12 * index; | |
// ----------------------------------------------------- 2 * Math.PI / 12 示意一个小时所占的弧度 | |
// ----------------------------------------------------- rad 示意以后点数的弧度 | |
const x = Math.cos(rad) * (arcRadius - 60) * rate; | |
const y = Math.sin(rad) * (arcRadius - 60) * rate; | |
context.fillText(item, x, y) // ------------------------ 填充数字 | |
}) | |
} | |
function clockDot() { // 60 份的刻度,和下面一样 | |
for(let i = 0; i < 60; i++) { | |
const rad = 2 * Math.PI / 60 * i; | |
const x = Math.cos(rad) * (arcRadius - 34) * rate; | |
const y = Math.sin(rad) * (arcRadius - 34) * rate; | |
context.beginPath() | |
context.arc(x, y, 4 * rate, 0, 360 * Math.PI/180) | |
if (i % 5 === 0) {context.fillStyle = '#000' // 整点的数字对应的刻度色彩高亮} else {context.fillStyle = '#ccc'} | |
context.fill() // -------------------------------------- 画圆填充色彩} | |
} | |
function clockPointer(hour, minute, second) { // ----------- 时针分针秒针 | |
// 时针 | |
const radHour = 2 * Math.PI / 12 * hour; // ----------------------------------------- 一小时的弧度 | |
const radHourMinute = 2 * Math.PI / 12 / 60 * minute; // ---------------------------- 一分钟的弧度 | |
const radHourtMinuteSecond = 2 * Math.PI / 12 / 60 / 60 * second; // ---------------- 一秒钟的弧度 | |
context.save() // --------------------- 保留以后环境状态,因为画别的分针,秒针时不能用当初的旋转后的环境 | |
context.rotate(radHour + radHourMinute + radHourtMinuteSecond) // -------------------- 旋转的总角度 | |
context.beginPath() | |
context.lineWidth = 10 * rate; | |
context.lineCap = 'round'; | |
context.strokeStyle='black' | |
context.moveTo(0, 10 * rate) | |
context.lineTo(0, (-arcRadius/2 + 20) * rate) | |
context.stroke() | |
context.restore() // ---------------------------------------------------- 旋转后,获取旋转之前的状态 | |
// 分针 | |
const radMinute = 2 * Math.PI / 60 * minute; | |
context.save() | |
context.beginPath() | |
context.rotate(radMinute) | |
context.lineCap = 'round' | |
context.strokeStyle='black' | |
context.lineWidth = 6 * rate; | |
context.moveTo(0, 10 * rate) | |
context.lineTo(0, (-arcRadius + 120) * rate) | |
context.stroke() | |
context.restore() | |
// 秒针 | |
const radSecond = 2 * Math.PI / 60 * second; | |
context.save() | |
context.beginPath() | |
context.rotate(radSecond) | |
context.lineCap = 'round' | |
context.lineWidth = 5 * rate; | |
context.moveTo(-3 * rate, 14 * rate) | |
context.lineTo(3 * rate, 14 * rate) | |
context.lineTo(1* rate, (-arcRadius + 90) * rate) | |
context.lineTo(-1* rate, (-arcRadius + 90) * rate) | |
context.fillStyle = 'blue' | |
context.fill() | |
context.restore() | |
// 圆点 | |
context.beginPath() | |
context.arc(0, 0, 4 * rate, 0, 2 * Math.PI) | |
context.fillStyle = '#fff' | |
context.fill()} | |
clockDot() | |
clockBorder() | |
clockNumber() | |
clockPointer(4, 15, 60) | |
setInterval(() => {context.clearRect(-300, -300, 600, 600) // ------------------------------ 革除后,从新绘制 | |
clockBorder() | |
clockDot() | |
clockNumber() | |
const date = new Date() | |
const hour = date.getHours() | |
const minute = date.getMinutes() | |
const second = date.getSeconds() | |
clockPointer(hour, minute, second) | |
}, 1000) | |
</script> | |
</body> | |
</html> |
实例 4:缩放图像
HTML5 滑动条
<input type="range"> 滑动条
- 留神:input 标签是单标签,即没有完结标签
- html 单标签有:
<input>
,<img>
,<link>
,<br>
,<hr>
,<meta>
<input type="range" min="1" max="10" step="1" value="3" />
- min:容许的最小值
- max:容许的最大值
- step:数字距离
- value:默认值
context.drawImage() — 重点了解这 9 个参数
context.drawImage(img,sx,sy,swidth,sheight,x,y,width,height)
- sx:要裁剪的原图像的起始点 x 坐标
- sy:要裁剪的原图像的起始点 y 坐标
- swidth:要裁剪的原图像的宽度
- sheight:要裁剪的原图像的高度
- x:裁剪完的图像要放在 canvas 上的起始点的 x 坐标
- y:裁剪完的图像要放在 canvas 上的起始点的 y 坐标
- width:裁剪完的图像要放在 canvas 上的宽度
- height:裁剪完的图像要放在 canvas 上的高度
<!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 style="background: #777;"> | |
<canvas id="canvas" width="400" height="400" | |
style="border: 1px solid red; display: block; margin: 0 auto;" | |
></canvas> | |
<input | |
type="range" | |
id="range" | |
style="display: block;margin: 20px auto;width: 400px" | |
min="0.1" | |
max="3.0" | |
step="0.01" | |
value="1.0" | |
> | |
<script> | |
const canvas = document.getElementById('canvas') | |
const slider = document.getElementById('range') | |
const context = canvas.getContext('2d') | |
const img = new Image() | |
img.src = './002.jpg' | |
if (img.complete) {init() | |
} else {img.onload = init} | |
function init() { | |
const scale = slider.value | |
drawImageByScale(scale) | |
} | |
function drawImageByScale(scale) { | |
const imgWidth = img.width * scale; | |
const imgHeight = img.height * scale; | |
const canvasWidth = canvas.width; | |
const canvasHeight = canvas.height; | |
const dx = canvasWidth/2 - imgWidth/2; | |
const dy = canvasHeight/2 - imgHeight/2; | |
context.clearRect(0, 0, canvasWidth, canvasWidth) | |
context.save() | |
context.beginPath() | |
context.drawImage(img, dx, dy, imgWidth, imgHeight) | |
context.restore()} | |
slider.onmousemove = function() { | |
const scale = slider.value | |
drawImageByScale(scale) | |
} | |
</script> | |
</body> | |
</html> |
实例 5:刮刮卡
<font color=red>content.globalCompositeOperation</font>
- <font color=red>context.globalCompositeOperation = ‘source-over’ 设置如何将源 (新的) 图像设置到指标 (已有) 图像上 </font>
- Composite:组合,合成
- destination:指标,起点
n
- 红色色示意源 (新的) 图像,蓝色示意指标 (已有) 图像
context.lineJoin
- <font color=red>context.lineJoin = ’round’ 设置当两条线相交时,边角的类型 </font>
- context.linWidth = 40
- context.linCap = ’round’
canvas 中获取鼠标的坐标有很大的偏移,不准确?
- <font color=red>canvas 的宽高必须在 canvas 标签中设置,不能用 css 设置,不然会偏移 </font>
代码
<!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> | |
* { | |
margin: 0; | |
padding: 0; | |
} | |
#canvas { // 设置 canvas 的背景,能够用别的图片作为底层,将 canvas 挪动到图片上重叠 | |
background-image: url('./4.jpg'); | |
background-position: center; | |
background-repeat: no-repeat; | |
background-size: cover; | |
border: 1px solid red; | |
} | |
</style> | |
</head> | |
<body> | |
<canvas id="canvas" width=400 height=330></canvas> | |
<script> | |
const canvas = document.getElementById('canvas') | |
const canvasWidth = canvas.width | |
const canvasHeight = canvas.height | |
if (canvas.getContext) { // ----------------------------------- 浏览器是否反对 canvas | |
const context = canvas.getContext('2d') | |
const img = new Image() | |
img.src = './002.jpg' | |
if (img.complete) {// --------------------------------- if...else 保障图标加载实现后执行 drawImages() | |
drawImages()} else {img.onload = drawImages} | |
let isEmit = false // 用于标记挪动时时候能够画线条了,因为只有鼠标按下后或者 touch 后失效,完结后又设为 false | |
let drawDots = 0 // 用来记录当初画的线段后,canvas 源图像通明的点,>2/ 3 则显示整个 canvas 背景 | |
function drawImages() {context.drawImage(img, 0, 0, canvas.width, canvas.height) | |
context.globalCompositeOperation = 'destination-out' | |
// ---------------------------------- context.globalCompositeOperation | |
// ---------------------------------- 'destination-out' 指标图被源图占据的局部将通明 | |
// 监听鼠标和 touch 事件 | |
canvas.addEventListener('mousedown', moveStart, false); | |
canvas.addEventListener('touchstart', moveStart, false); | |
canvas.addEventListener('mousemove', move, false); | |
canvas.addEventListener('touchmove', move, false); | |
canvas.addEventListener('mouseup', moveEnd, false); | |
canvas.addEventListener('touchend', moveEnd, false); | |
context.lineWidth = 30 | |
context.lineCap = 'round' | |
context.lineJoin = 'round' | |
context.strokeStyle = 'white' | |
} | |
function moveStart(e) { | |
isEmit = true // 点击后才准许挪动时画图 | |
drawLineFn(e) | |
} | |
function move(e) {if (!isEmit) return; // 不成立,则返回 | |
drawLineFn(e) | |
} | |
function moveEnd(e) { | |
isEmit = false // 完结后挪动不能再画图 | |
drawLineFn(e) | |
paintAll() // 判断是否全副显示背景图,当画到肯定水平,间接能够显示全副} | |
function getDot(e) {const dotx = e.type.match('mouse') ? e.clientX : e.changedTouches[0].clientX; | |
const doty = e.type.match('mouse') ? e.clientY : e.changedTouches[0].clientY; | |
return {dotx, doty} | |
} | |
function drawLineFn(e) {const {dotx, doty} = getDot(e) | |
context.save() | |
context.beginPath() | |
context.moveTo(dotx, doty) | |
context.lineTo(dotx + 0.11, doty + 0.1) // 画线 | |
context.stroke() | |
context.closePath() | |
context.restore()} | |
function paintAll() {const imageData = context.getImageData(0, 0, canvasWidth, canvasHeight) | |
const allDots = imageData.width * imageData.height; // ----------- 图片所有的点 | |
for(let i = 0; i < allDots; i++) {if(imageData.data[i*4 + 3] === 0) { // ------------------------- 统计通明的点 | |
drawDots++ // 统计通明的点 | |
} | |
} | |
if (drawDots > allDots * 2/3) { // 通明占总数点的比例 | |
context.save() | |
context.beginPath() | |
context.fillRect(0, 0, canvasWidth, canvasHeight) // 用源图占满整个指标图,globalCompositeOperation 的使用 | |
context.closePath() | |
context.restore()} | |
} | |
}; | |
</script> | |
</body> | |
</html> |
材料
canvas-api:https://www.w3school.com.cn/t…
MDN:https://developer.mozilla.org…
ImageData 对象:https://developer.mozilla.org…
canvas 转成图片保留:https://segmentfault.com/a/11…
粒子动画 3:https://juejin.im/post/684490…
粒子动画 1:https://juejin.im/post/684490…
时钟动画:https://www.imooc.com/video/1…
globalCompositeOperation1:https://www.w3school.com.cn/t…
globalCompositeOperation2:https://www.w3school.com.cn/t…
刮刮卡 1:https://juejin.im/post/684490…
刮刮卡 2:https://juejin.im/post/684490…