1. Basic API
2. Billboard
3. Text Editor
4. Recording
5. What's more
属性描述
fillStyle | 设置或返回用于填充绘画的颜色、渐变或模式 |
strokeStyle | 设置或返回用于笔触的颜色、渐变或模式 |
shadowColor | 设置或返回用于阴影的颜色 |
shadowBlur | 设置或返回用于阴影的模糊级别 |
shadowOffsetX | 设置或返回阴影距形状的水平距离 |
shadowOffsetY | 设置或返回阴影距形状的垂直距离 |
方法描述
createLinearGradient() | 创建线性渐变(用在画布内容上) |
createPattern() | 在指定的方向上重复指定的元素 |
createRadialGradient() | 创建放射状/环形的渐变(用在画布内容上) |
addColorStop() | 规定渐变对象中的颜色和停止位置 |
fill() | 填充当前绘图(路径) |
stroke() | 绘制已定义的路径 |
beginPath() | 起始一条路径,或重置当前路径 |
moveTo() | 把路径移动到画布中的指定点,不创建线条 |
closePath() | 创建从当前点回到起始点的路径 |
lineTo() | 添加一个新点,然后在画布中创建从该点到最后指定点的线条 |
clip() | 从原始画布剪切任意形状和尺寸的区域 |
quadraticCurveTo() | 创建二次贝塞尔曲线 |
bezierCurveTo() | 创建三次方贝塞尔曲线 |
arc() | 创建弧/曲线(用于创建圆形或部分圆) |
arcTo() | 创建两切线之间的弧/曲线 |
isPointInPath() | 如果指定的点位于当前路径中,则返回 true,否则返回 false |
function drawImage(
img: CanvasImageSource,
dx: number,
dy: number,
dw: number,
dh: number,
type?: string,
strokeColor?: string
) {
if (type === 'circle') {
this.ctx.save();
const r = dw / 2;
const cx = dx + r;
const cy = dy + r;
this.ctx.beginPath();
this.ctx.arc(cx, cy, r, 0, 2 * Math.PI);
this.ctx.clip();
this.ctx.drawImage(img, dx, dy, dw, dh);
this.ctx.closePath();
this.ctx.restore();
} else {
this.ctx.drawImage(img, dx, dy, dw, dh);
}
}
var sun = new Image();
var moon = new Image();
var earth = new Image();
function init() {
sun.src = 'https://mdn.mozillademos.org/files/1456/Canvas_sun.png';
moon.src = 'https://mdn.mozillademos.org/files/1443/Canvas_moon.png';
earth.src = 'https://mdn.mozillademos.org/files/1429/Canvas_earth.png';
window.requestAnimationFrame(draw);
}
function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
ctx.globalCompositeOperation = 'destination-over';
ctx.clearRect(0, 0, 300, 300); // clear canvas
ctx.fillStyle = 'rgba(0, 0, 0, 0.4)';
ctx.strokeStyle = 'rgba(0, 153, 255, 0.4)';
ctx.save();
ctx.translate(150, 150);
// Earth
var time = new Date();
ctx.rotate(((2 * Math.PI) / 60) * time.getSeconds() + ((2 * Math.PI) / 60000) * time.getMilliseconds());
ctx.translate(105, 0);
ctx.fillRect(0, -12, 40, 24); // Shadow
ctx.drawImage(earth, -12, -12);
// Moon
ctx.save();
ctx.rotate(((2 * Math.PI) / 6) * time.getSeconds() + ((2 * Math.PI) / 6000) * time.getMilliseconds());
ctx.translate(0, 28.5);
ctx.drawImage(moon, -3.5, -3.5);
ctx.restore();
ctx.restore();
ctx.beginPath();
ctx.arc(150, 150, 105, 0, Math.PI * 2, false); // Earth orbit
ctx.stroke();
ctx.drawImage(sun, 0, 0, 300, 300);
window.requestAnimationFrame(draw);
}
init();
From MDN
header
footer
content
Approach 1
Approach 2
<style>
canvas {
width: 100%;
background-color: #fff;
}
textarea {
position: absolute;
opacity: 0;
z-index: -1;
}
</style>
<div nz-col nzSpan="20">
<canvas #textEditor width="2000px" height="900px"></canvas>
<textarea #textArea></textarea>
</div>
initTextEditorLogic() {
this.textElement.addEventListener('compositionstart', this.chineseTextInputHandler, false);
this.textElement.addEventListener('input', this.normalTextInputHandler, false);
this.textElement.addEventListener('compositionend', this.commonTextInputEndHandler, false);
}
chineseTextInputHandler = () => {
this.textInputType = TextType.CHINESE;
};
normalTextInputHandler = (e) => {
if (this.textInputType === TextType.CHINESE) {
return;
}
// 处理英文输入
this.updateTextList(e.data);
this.drawTextEditorLogic(true);
};
commonTextInputEndHandler = (e) => {
if (this.textInputType === TextType.CHINESE) {
this.updateTextList(e.data);
this.drawTextEditorLogic(true);
this.textInputType = TextType.ENGLISH;
}
};
switch (e.key) {
case 'Meta':
case 'Control':
this.isCommandKey = true;
break;
case 'a':
this.onAKeyHandler();
break;
case 'Backspace':
this.onBackspaceKeyHandler();
break;
case 'ArrowRight':
this.onHorizontalArrowKeyHandler('right');
break;
case 'ArrowLeft':
this.onHorizontalArrowKeyHandler('left');
break;
case 'ArrowUp':
this.onVerticalArrowKeyHandler('up');
break;
case 'ArrowDown':
this.onVerticalArrowKeyHandler('down');
break;
case 'Enter':
this.onEnterKeyHandler();
break;
}
this.canvas.addEventListener('click', this.focusEditorHandler.bind(this));
focusEditorHandler(e) {
this.canvas.style.borderColor = '#f00';
const clientX = e.offsetX / this.scaleRatio;
const clientY = e.offsetY / this.scaleRatio;
/* Cursor 位置计算 */
const { colIndex, rowIndex } = this.calculateCursorPos(clientX, clientY);
...
this.drawTextEditorLogic(true);
}
drawTextEditorLogic(raf: boolean = false) {
this.relocateTextList();
for (let i = 0; i < this.textList.length; i++) {
let str = '';
for (let j = 0; j < this.textList[i].length; j++) {
if (this.isTextSelected && !this.isSelectAll && this.cursorPos[0] === i && this.cursorPos[1] === j) {
str += '|';
}
str += this.textList[i][j];
}
...
startRecording() {
const stream = (this.canvas as any).captureStream();
this.recorder = new MediaRecorder(stream, { mimeType: 'video/webm' });
const data = [];
this.recorder.ondataavailable = (event) => {
if (event.data && event.data.size) {
data.push(event.data);
}
};
this.recorder.onstop = () => {
const url = URL.createObjectURL(new Blob(data, { type: 'video/webm' }));
this.videoUrl$.next(
this.sanitizer.bypassSecurityTrustUrl(url)
);
};
this.recorder.start();
this.recordVideo$.next(true);
}
Online PhotoShop
ImageData.data Read only
Is a Uint8ClampedArray representing a one-dimensional array containing the data in the RGBA order, with integer values between 0 and 255 (inclusive).
ImageData.height Read only
Is an unsigned long representing the actual height, in pixels, of the ImageData.
ImageData.width Read only
Is an unsigned long representing the actual width, in pixels, of the ImageData.
let blockSize = 5;
try {
data = context.getImageData(0, 0, width, height);
} catch (e) {
return defaultRGB;
}
length = data.data.length;
while (i < length) {
++count;
rgb.r += data.data[i];
rgb.g += data.data[i + 1];
rgb.b += data.data[i + 2];
i += blockSize * 4;
}
context.putImageData(imgData, 0, 0);