diff --git a/learn-canvas/1-create-canvas.ts b/learn-canvas/1-create-canvas.ts index 95a5ef7..8ad15c8 100644 --- a/learn-canvas/1-create-canvas.ts +++ b/learn-canvas/1-create-canvas.ts @@ -4,11 +4,11 @@ */ import {initCanvas} from './share'; +import {GridSize} from './constant'; function syDraw() { const drawReact = (ctx: CanvasRenderingContext2D) => { - ctx.fillStyle = 'blue'; - ctx.fillRect(40, 40, 120, 80); + ctx.fillRect(GridSize, GridSize, GridSize * 2, 80); } let ctx = initCanvas(); diff --git a/learn-canvas/17-transform2.ts b/learn-canvas/17-transform2.ts new file mode 100644 index 0000000..05dd3c6 --- /dev/null +++ b/learn-canvas/17-transform2.ts @@ -0,0 +1,187 @@ +/** + * @author 素燕(我有个公众号:素燕) + * @description transform 的理解 + * scale, rotate, translate (move), and skew the context + * https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-settransform-dev + */ + +import { initCanvas, SYAxisPos } from './share'; +import { GridSize, TextColor } from './constant'; + +function syRunDemo() { + const drawSkew = (ctx: CanvasRenderingContext2D) => { + ctx.save(); + ctx.strokeStyle = '#1c4587'; + // ctx.translate(origin.x, origin.y); + ctx.setTransform(4, .1, .1, 2, 1, 2); + let width = 120; + let height = 80; + // ctx.strokeRect(-width - 40, -height - 40, width, height); + ctx.fillRect(40, 40, width, height); + ctx.restore(); + } + + const move = (ctx: CanvasRenderingContext2D, x: number, y: number) => { + ctx.save(); + + const drawArrow = (dash: number[]) => { + ctx.beginPath(); + ctx.lineJoin = 'round'; + ctx.setLineDash(dash); + ctx.moveTo(0, 0); + ctx.lineTo(GridSize * 3, GridSize * 3); + ctx.lineTo(3 * GridSize - GridSize * 0.5, 3*GridSize); + ctx.moveTo(GridSize * 3, GridSize * 3); + ctx.lineTo(3 * GridSize, 3*GridSize - GridSize * 0.5); + ctx.stroke(); + ctx.closePath(); + } + + drawArrow([8]); + + ctx.fillStyle = TextColor; + ctx.font = '30px Times'; + ctx.fillText('Translate 位置移动', GridSize * 3, GridSize); + + /** + * 平移,相当于修改 e、f 的值,初始矩阵 + * | a c e | + * | b d f | + * | 0 0 1 | + * x' = ax + cy + e + * y' = bx + dy + f + * + * | 1 0 0 | + * | 0 1 0 | + * | 0 0 1 | + * + * | 1 0 40 | + * | 0 1 20 | + * | 0 0 1 | + * + * x' = 1*x + 0*y + 40 = x + 40 + * y' = 0*x + 1*y + 20 = y + 20 + * + */ + ctx.transform(1, 0, 0, 1, x, y); + drawArrow([0]); + + ctx.restore(); + } + + const scale = (ctx: CanvasRenderingContext2D, x: number, y: number) => { + ctx.save(); + ctx.fillStyle = TextColor; + ctx.font = '30px Times'; + ctx.fillText('Scale 缩放', -GridSize * 3, GridSize); + + ctx.restore(); + ctx.save(); + ctx.globalAlpha = 0.2; + ctx.fillRect(-GridSize*6, GridSize, GridSize*3, GridSize*2); + + /** + * 平移,相当于修改 e、f 的值,初始矩阵 + * | a c e | + * | b d f | + * | 0 0 1 | + * x' = ax + cy + e + * y' = bx + dy + f + * + * 初始矩阵: + * | 1 0 0 | + * | 0 1 0 | + * | 0 0 1 | + * + * 缩放后的矩阵 + * | 4 0 0 | + * | 0 2 0 | + * | 0 0 1 | + * + * x' = 4*x + 0*y + 0 = 4x + * y' = 0*x + 2*y + 0 = 2y + * + */ + ctx.transform(x, 0, 0, y, 0, 0); + // 等价于 scale + // ctx.scale(x, y) + ctx.globalAlpha = 1; + // x,y,width,height 均按照缩放比例进行缩放 + /** + * x = x * scaleX + * y = y * scaleY + * width = width * scaleX + * height = height * scaleY + */ + ctx.fillRect(-GridSize*6, GridSize, GridSize*3, GridSize*2); + + ctx.restore(); + } + + const rotate = (ctx: CanvasRenderingContext2D, angle: number) => { + ctx.save(); + ctx.fillStyle = TextColor; + ctx.font = '30px Times'; + ctx.fillText('Rotate旋转', GridSize * 3, -0.5*GridSize); + + ctx.restore(); + ctx.save(); + + ctx.save(); + + const drawArrow = (dash: number[]) => { + ctx.beginPath(); + ctx.lineJoin = 'round'; + ctx.strokeStyle = '#32C5FF' + ctx.setLineDash(dash); + ctx.moveTo(0, 0); + ctx.lineTo(GridSize * 3, -GridSize * 3); + ctx.lineTo(3 * GridSize - GridSize * 0.5, -3*GridSize); + ctx.moveTo(GridSize * 3, -GridSize * 3); + ctx.lineTo(3 * GridSize, -3*GridSize + GridSize * 0.5); + ctx.stroke(); + ctx.closePath(); + } + + drawArrow([8]); + + /** + * 平移,相当于修改 e、f 的值,初始矩阵 + * | a c e | + * | b d f | + * | 0 0 1 | + * x' = ax + cy + e + * y' = bx + dy + f + * + * 初始矩阵: + * | 1 0 0 | + * | 0 1 0 | + * | 0 0 1 | + * + * 缩放后的矩阵 + * | cosθ -sinθ 0 | + * | sinθ cosθ 0 | + * | 0 0 1 | + * + * x' = cos45*x + -sin45*y + 0 = cos45*x - sin45*y + * y' = sin45*x + cos45*y + 0 = sin45*x + cos45*y + * + */ + ctx.transform(Math.cos(angle), Math.sin(angle), -Math.sin(angle), Math.cos(angle), 0, 0); + drawArrow([0]); + + ctx.restore(); + } + + // 1. 创建 canvas + let ctx = initCanvas(SYAxisPos.Center); + + move(ctx, GridSize*2, GridSize); + scale(ctx, 0.5, 2); + rotate(ctx, 0.4); + + // drawSkew(ctx); + +} + +syRunDemo(); \ No newline at end of file diff --git a/learn-canvas/2-shape-rect.ts b/learn-canvas/2-shape-rect.ts index 551d21d..9ba6473 100644 --- a/learn-canvas/2-shape-rect.ts +++ b/learn-canvas/2-shape-rect.ts @@ -4,20 +4,19 @@ */ import {initCanvas} from './share'; +import {GridSize} from './constant'; function syRunDrawRectDemo() { const drawReact = (ctx: CanvasRenderingContext2D) => { - ctx.fillStyle = '#555'; - ctx.fillRect(40, 40, 80, 80); + ctx.fillRect(GridSize * 8, GridSize, GridSize * 2, GridSize * 2); } const strokeReact = (ctx: CanvasRenderingContext2D) => { - ctx.strokeStyle = '#555'; - ctx.strokeRect(200, 40, 120, 80); + ctx.strokeRect(GridSize * 8, GridSize * 4, GridSize * 2, GridSize); } const clearReact = (ctx: CanvasRenderingContext2D) => { - ctx.clearRect(120, 200, 80, 80); + ctx.clearRect(GridSize * 9, GridSize * 1, GridSize * 1, GridSize * 1); } const drawColorPannel = (ctx: CanvasRenderingContext2D) => { @@ -25,7 +24,7 @@ function syRunDrawRectDemo() { for (let j = 0; j < 6; j++){ ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' + Math.floor(255-42.5*j) + ',0)'; - ctx.fillRect(j*25, i*25, 25, 25); + ctx.fillRect(GridSize + j*GridSize, GridSize + i*GridSize, GridSize, GridSize); } } } diff --git a/learn-canvas/3-shape-path.ts b/learn-canvas/3-shape-path.ts index cf065a1..8e0374f 100644 --- a/learn-canvas/3-shape-path.ts +++ b/learn-canvas/3-shape-path.ts @@ -4,27 +4,28 @@ */ import { initCanvas } from './share'; +import {GridSize} from './constant'; function syRunDrawPathDemo() { -// 绘制一根线 -const drawLine = (ctx: CanvasRenderingContext2D) => { - // 不调用 beginPath 会有意想不到的结果 - ctx.beginPath(); - // 移动移动到某个位置,可以作为绘图的起点,或者绘制之间不连接的 path - // 线宽 - ctx.lineWidth = 30; - // 线顶点的样式 'butt', 'round', 'square' - // butt 默认样式,不会导致线变长 - // round 圆头,会导致两顶点处的线变长 lineWidth/2 - // square 方头,会导致两顶点处线变长 lineWidth/2 - ctx.lineCap = 'round'; - - // 在画线的过程中需要注意最终线被画到了那个区域 - ctx.moveTo(80, 120); - // lineTo 画一条直线 - ctx.lineTo(320, 120); - ctx.stroke(); -} + // 绘制一根线 + const drawLine = (ctx: CanvasRenderingContext2D) => { + // 不调用 beginPath 会有意想不到的结果 + ctx.beginPath(); + // 移动移动到某个位置,可以作为绘图的起点,或者绘制之间不连接的 path + // 线宽 + ctx.lineWidth = 20; + // 线顶点的样式 'butt', 'round', 'square' + // butt 默认样式,不会导致线变长 + // round 圆头,会导致两顶点处的线变长 lineWidth/2 + // square 方头,会导致两顶点处线变长 lineWidth/2 + ctx.lineCap = 'round'; + + // 在画线的过程中需要注意最终线被画到了那个区域 + ctx.moveTo(GridSize * 2, GridSize * 2); + // lineTo 画一条直线 + ctx.lineTo(GridSize * 5, GridSize * 5); + ctx.stroke(); + } const drawLineJoinStyle = (ctx: CanvasRenderingContext2D) => { ctx.beginPath(); @@ -42,7 +43,6 @@ const drawLine = (ctx: CanvasRenderingContext2D) => { // ctx.lineJoin = 'round'; let dashs = ctx.getLineDash(); - console.log('dashs = ', dashs); ctx.setLineDash([20]); ctx.stroke(); @@ -68,45 +68,39 @@ const drawLine = (ctx: CanvasRenderingContext2D) => { ctx.fill(); } -const drawArcs = (ctx: CanvasRenderingContext2D) => { - // arc(x, y, radius, startAngle, endAngle, counterclockwise) - ctx.beginPath(); - // false 顺时针 true 逆时针 - // 弧度和角度 - // 弧度是角的度量单位 - // 弧长等于半径的弧,其所对的圆心角为一弧度 - // 弧度表示:弧长与半径长相等所对应的角度 - // 圆的弧长为 2πr,一个圆的弧度为 2πr / r = 2π,π为圆周率,约为 3.14 2π = 2*3.14=6.28 - // 1π 为 180度,1度 = π / 180, 1弧度 = 180 / π - - // 起点为 3 点钟方向 - // 画一弧度 - // ctx.arc(80, 160, 40, 0, 1, false); // Outer circle - // 画 90 度 - // ctx.arc(80, 160, 40, 0, Math.PI / 180 * 90); - ctx.arc(80, 160, 40, 0, Math.PI / 180 * 360, false); - - // 如何才能让起点为 12 点钟方向 - // ctx.arc(80, 160, 40, Math.PI / 180 * 270, Math.PI / 180 * 180, false); + const drawArcs = (ctx: CanvasRenderingContext2D) => { + // arc(x, y, radius, startAngle, endAngle, counterclockwise) + ctx.beginPath(); + // false 顺时针 true 逆时针 + // 弧度和角度 + // 弧度是角的度量单位 + // 弧长等于半径的弧,其所对的圆心角为一弧度 + // 弧度表示:弧长与半径长相等所对应的角度 + // 圆的弧长为 2πr,一个圆的弧度为 2πr / r = 2π,π为圆周率,约为 3.14 2π = 2*3.14=6.28 + // 1π 为 180度,1度 = π / 180, 1弧度 = 180 / π + + // 起点为 3 点钟方向 + // 画一弧度 + // ctx.arc(80, 160, 40, 0, 1, false); // Outer circle + // 画 90 度 + // ctx.arc(80, 160, 40, 0, Math.PI / 180 * 90); + ctx.arc(80, 160, 40, 0, Math.PI / 180 * 360, false); + + // 如何才能让起点为 12 点钟方向 + // ctx.arc(80, 160, 40, Math.PI / 180 * 270, Math.PI / 180 * 180, false); - ctx.stroke(); -} + ctx.stroke(); + } // 1. 创建 canvas let ctx = initCanvas(); - // 初始化画布的一些属性 - // 设置线的颜色 - ctx.strokeStyle = 'red'; - ctx.fillStyle = '#222'; - ctx.lineWidth = 4; - // 在画布中画一个矩形区域 - // drawLine(ctx); - // drawRect(ctx); - // drawTriangle(ctx); + drawLine(ctx); + drawRect(ctx); + drawTriangle(ctx); drawArcs(ctx); - // drawLineJoinStyle(ctx); + drawLineJoinStyle(ctx); } syRunDrawPathDemo(); \ No newline at end of file diff --git a/learn-canvas/constant.ts b/learn-canvas/constant.ts index 1755a02..a0287c4 100644 --- a/learn-canvas/constant.ts +++ b/learn-canvas/constant.ts @@ -5,4 +5,8 @@ export const BgColor = '#222'; export const TextColor = '#fff'; -export const LineColor = ''; \ No newline at end of file +export const ContentColor = '#C741B1'; +export const StrokeColor = '#FEFF06'; +export const GridLineColor = '#42A0B8'; +export const AxisLineColor = '#fff'; +export const GridSize = 80; \ No newline at end of file diff --git a/learn-canvas/index.html b/learn-canvas/index.html index cdad018..3e4cb07 100644 --- a/learn-canvas/index.html +++ b/learn-canvas/index.html @@ -46,8 +46,9 @@ - + + \ No newline at end of file diff --git a/learn-canvas/share.ts b/learn-canvas/share.ts index e18572d..5101411 100644 --- a/learn-canvas/share.ts +++ b/learn-canvas/share.ts @@ -3,15 +3,40 @@ * @description 使用 JavaScript 创建 Canvas */ -export function initCanvas(): CanvasRenderingContext2D { +import { + BgColor, + TextColor, + GridLineColor, + AxisLineColor, + GridSize, + ContentColor, + StrokeColor +} from './constant'; + +export interface SYPoint { + x: number; + y: number; +} + +export enum SYAxisPos { + TopLeft = 'topLeft', + Center = 'center' +} + +export function initCanvas(originAxis: SYAxisPos = SYAxisPos.TopLeft): CanvasRenderingContext2D { + const pageWidth = document.documentElement.clientWidth; + const pageHeight = document.documentElement.clientHeight; const parentId = 'canvas-warp'; - const canvasWidth = 1400; - const canvasHeight = 800; - const gridSpace = 40; - const lineColor = '#cec'; - const edge = 20; - const color = 'blue'; - let dprValue = window.devicePixelRatio || 1; + const canvasWidth = pageWidth; + const canvasHeight = pageHeight; + const gridSpace = GridSize; + const lineColor = GridLineColor; + const color = AxisLineColor; + const xCount = Math.ceil(canvasWidth / gridSpace); + const yCount = Math.ceil(canvasHeight / gridSpace); + const centerX = Math.floor(xCount / 2) * gridSpace; + const centerY = Math.floor(yCount / 2) * gridSpace; + const dprValue = window.devicePixelRatio || 1; const createACanvas = () => { let canvas = document.createElement('canvas') as HTMLCanvasElement; @@ -22,7 +47,7 @@ export function initCanvas(): CanvasRenderingContext2D { // 设置画布的 CSS 样式 canvas.style.width = `${canvasWidth}px`; canvas.style.height = `${canvasHeight}px`; - canvas.style.backgroundColor = '#222'; + canvas.style.backgroundColor = BgColor; return canvas; } @@ -40,90 +65,120 @@ export function initCanvas(): CanvasRenderingContext2D { canvas.style.left = '0'; canvas.style.width = '100%'; canvas.style.height = '100%'; - // 可以设置字符间距 - canvas.style.letterSpacing = '0px'; return canvas; } // 绘制网格 const drawGrid = (ctx: CanvasRenderingContext2D) => { - let xCount = canvasHeight / gridSpace; - let yCount = canvasWidth / gridSpace; - - const drawValue = (i, isX) => { - ctx.font = '12px'; - ctx.textAlign = 'left'; - ctx.textBaseline = 'top'; - ctx.fillStyle = '#222'; - if (isX) { - ctx.fillText(`${i * gridSpace}`, 0, gridSpace * i); - } - else { - ctx.fillText(`${i * gridSpace}`, gridSpace * i, 0); - } - } + ctx.strokeStyle = lineColor; + ctx.lineWidth = 1; // 绘制 x 轴的线 - for (let i = 0; i < xCount; i++) { + for (let i = 0; i < yCount; i++) { ctx.beginPath(); ctx.moveTo(0, gridSpace * i); ctx.lineTo(canvasWidth, gridSpace * i); - ctx.strokeStyle = lineColor; - ctx.lineWidth = 1; ctx.stroke(); - drawValue(i, true); - } // 绘制 y 轴的线 - for (let i = 0; i < yCount; i++) { + for (let i = 0; i < xCount; i++) { ctx.beginPath(); ctx.moveTo(gridSpace * i, 0); ctx.lineTo(gridSpace * i, canvasHeight); - ctx.strokeStyle = lineColor; - ctx.lineWidth = 1; ctx.stroke(); - - drawValue(i, false); } }; - const drawX = (ctx: CanvasRenderingContext2D) => { - let y = edge / 2; + const drawLeftTopX = (ctx: CanvasRenderingContext2D) => { + ctx.strokeStyle = color; + ctx.lineWidth = 2; + ctx.beginPath(); - ctx.moveTo(edge / 2, y); - ctx.lineTo(canvasWidth - edge, y); + ctx.moveTo(0, 10); + ctx.lineTo(canvasWidth, 4); + ctx.stroke(); + } + + const drawLeftTopY = (ctx: CanvasRenderingContext2D) => { ctx.strokeStyle = color; - ctx.lineWidth = 1; - ctx.moveTo(canvasWidth - edge / 2 - edge, y / 2); - ctx.lineTo(canvasWidth - edge, y); - ctx.lineTo(canvasWidth - edge / 2 - edge, y / 2 + edge / 2); + ctx.lineWidth = 2; + + ctx.beginPath(); + ctx.moveTo(4, 0); + ctx.lineTo(4, canvasHeight); ctx.stroke(); + } - ctx.fillStyle = color; - ctx.font = '16px Times'; - ctx.fillText('X', canvasWidth - edge + 2, y / 2); + const drawTopLeftValue = (ctx: CanvasRenderingContext2D) => { + let xCount = canvasHeight / gridSpace; + let yCount = canvasWidth / gridSpace; + + ctx.strokeStyle = lineColor; + ctx.lineWidth = 1; + ctx.font = '12px'; + ctx.textAlign = 'left'; + ctx.textBaseline = 'top'; + ctx.fillStyle = TextColor; + + drawRedPoint({ + x: 6, + y: 8 + }, ctx); + + for (let i = 1; i < xCount; i++) { + ctx.fillText(`${i * gridSpace}`, 10, gridSpace * i); + } + + for (let i = 1; i < yCount; i++) { + ctx.fillText(`${i * gridSpace}`, gridSpace * i, 10); + } } - const drawY = (ctx: CanvasRenderingContext2D) => { - let xLeft = edge / 2; - ctx.beginPath(); - ctx.moveTo(xLeft, xLeft); - // 画线 - ctx.lineTo(xLeft, canvasHeight - edge); + const drawCenterX = (ctx: CanvasRenderingContext2D) => { + ctx.strokeStyle = color; + ctx.lineWidth = 2; - // 画箭头 - ctx.moveTo(xLeft / 2, canvasHeight - edge / 2 - edge); - ctx.lineTo(xLeft, canvasHeight - edge); - ctx.lineTo(xLeft + xLeft / 2, canvasHeight - edge / 2 - edge); + ctx.beginPath(); + ctx.moveTo(0, centerY); + ctx.lineTo(canvasWidth, centerY); + ctx.stroke(); + } + const drawCenterY = (ctx: CanvasRenderingContext2D) => { ctx.strokeStyle = color; - ctx.lineWidth = 1; + ctx.lineWidth = 2; + + ctx.beginPath(); + ctx.moveTo(centerX, 0); + ctx.lineTo(centerX, canvasHeight); ctx.stroke(); + } + + const drawCenterValue = (ctx: CanvasRenderingContext2D) => { + ctx.strokeStyle = lineColor; + ctx.lineWidth = 1; + ctx.font = '12px Times'; + ctx.textAlign = 'left'; + ctx.textBaseline = 'top'; + ctx.fillStyle = TextColor; - ctx.fillStyle = color; - ctx.font = '16px Times'; - ctx.fillText('Y', xLeft / 2, canvasHeight - edge + 2); + drawRedPoint({ + x: centerX, + y: centerY + }, ctx); + + // 绘制 x 轴的线 + for (let i = 1; i < xCount / 2; i++) { + ctx.fillText(`${i * gridSpace}`, centerX + i * gridSpace - 8, centerY + 4); + ctx.fillText(`-${i * gridSpace}`, centerX - i * gridSpace - 8, centerY + 4); + } + + // 绘制 y 轴的线 + for (let i = 1; i < yCount; i++) { + ctx.fillText(`${i * gridSpace}`, centerX + 4, centerY + i * gridSpace - 4); + ctx.fillText(`-${i * gridSpace}`, centerX + 4, centerY - i * gridSpace + 4); + } } // 1. 创建 canvas @@ -134,10 +189,19 @@ export function initCanvas(): CanvasRenderingContext2D { // 2. 画网格 drawGrid(ctx); - // 画 X 轴线 - drawX(ctx); - // 画 Y 轴线 - drawY(ctx); + if (originAxis === SYAxisPos.TopLeft) { + // 画 X 轴线 + drawLeftTopX(ctx); + // 画 Y 轴线 + drawLeftTopY(ctx); + // 画坐标上的值 + drawTopLeftValue(ctx); + } + else { + drawCenterX(ctx); + drawCenterY(ctx); + drawCenterValue(ctx); + } // 3. 把 canvas 添加到 DOM 节点中 let parentDom = document.getElementById(parentId); @@ -146,13 +210,28 @@ export function initCanvas(): CanvasRenderingContext2D { // 4.创建真正要绘图的 canvas let drawCanvas = createDrawerCanvas(); let drawCtx = drawCanvas.getContext('2d') as CanvasRenderingContext2D; + drawCtx.fillStyle = ContentColor; + drawCtx.strokeStyle = StrokeColor; + drawCtx.lineWidth = 4; drawCtx.setTransform(dprValue, 0, 0, dprValue, 0, 0); + if (originAxis === SYAxisPos.Center) { + drawCtx.translate(centerX, centerY); + } parentDom.appendChild(drawCanvas); // 4. 在画布中画一个矩形区域 return drawCtx; } +export const drawRedPoint = (p: SYPoint, ctx: CanvasRenderingContext2D) => { + ctx.save(); + ctx.beginPath(); + ctx.fillStyle = 'red'; + ctx.arc(p.x, p.y, 6, 0, 2 * Math.PI); + ctx.fill(); + ctx.restore(); +} + export const drawPoint = (p, ctx: CanvasRenderingContext2D) => { ctx.beginPath(); ctx.arc(p.x, p.y, 4, 0, 2 * Math.PI);