Play with Canvas

Menu

1. Basic API

2. Billboard

3. Text Editor

4. Recording

5. What's more

Basic Usage

Text, line, rectangle, circle, image, animations, etc

Basic Usage

Color, Shadow and Styles

属性描述

fillStyle 设置或返回用于填充绘画的颜色、渐变或模式
strokeStyle 设置或返回用于笔触的颜色、渐变或模式
shadowColor 设置或返回用于阴影的颜色
shadowBlur 设置或返回用于阴影的模糊级别
shadowOffsetX 设置或返回阴影距形状的水平距离
shadowOffsetY 设置或返回阴影距形状的垂直距离

方法描述

createLinearGradient() 创建线性渐变(用在画布内容上)
createPattern() 在指定的方向上重复指定的元素
createRadialGradient() 创建放射状/环形的渐变(用在画布内容上)
addColorStop() 规定渐变对象中的颜色和停止位置

Path

fill() 填充当前绘图(路径)
stroke() 绘制已定义的路径
beginPath() 起始一条路径,或重置当前路径
moveTo() 把路径移动到画布中的指定点,不创建线条
closePath() 创建从当前点回到起始点的路径
lineTo() 添加一个新点,然后在画布中创建从该点到最后指定点的线条
clip() 从原始画布剪切任意形状和尺寸的区域
quadraticCurveTo() 创建二次贝塞尔曲线
bezierCurveTo() 创建三次方贝塞尔曲线
arc() 创建弧/曲线(用于创建圆形或部分圆)
arcTo() 创建两切线之间的弧/曲线
isPointInPath() 如果指定的点位于当前路径中,则返回 true,否则返回 false

Image

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);
    }
}

Animation

  1. Clear the canvas
  2. Save the canvas state
  3. Draw animated shapes
  4. Restore the canvas state

Animation

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

Billboard

header

footer

content

  1. ELEMENTS: Title, subtitle, description, logo, etc.
  2. POSITION: Auto position calculation.
  3. THEME: Configurations.
  1. SHAPE: Table, list, billboard
  2. EFFECT: Text eclipse, word wrap, circular avatar, rectangle border, etc.
  3. POSITION: Auto position calculation, center content, linear-gradient border, etc.
  4. SIZE: Cell spans, resizable width and height.
  1. ELEMENTS: Title, subtitle, description, logo, etc.
  2. POSITION: Auto position calculation.
  3. THEME: Configurations.

Approach 1

  • Get the `<canvas>` element
  • Parse configurations and load assets
  • Draw shapes with context
  • Notify caller

 

Approach 2

  • Parse configurations and load assets
  • Generate a blank <canvas> element
  • Draw shapes with context
  • Return resource url

Text Editor

Text Editor

Text input and output

  • Delegate input manipulation
  • Support Chinese character
  • Support hot keys
  • Display cursor
  • Editor layout, etc

Text Editor - Delegate input manipulation

<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>

Text Editor - Support Chinese character

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;
  }
};

Text Editor - Support hot keys

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;
}

Text Editor - Display cursor

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];
    }
    
    ...

Text Editor - Editor layout

  1. Focus state hint
  2. Text auto wrap
  3. Select all hint
  4. ......

Recording

Make a video

  • Specify stream
  • Initialize recorder
  • Start recording
  • ...
  • Stop recording
  • Create url

MediaRecorder

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);
}

Demo

What's more?

Usage of getImageData

Online PhotoShop

ImageData

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.

Get rgba info from each block

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);

Get theme color

Get video filter

WebGL (canvas 3D)

three.js, luma.gl, echarts gl, etc

Q&A