首页 > 解决方案 > HTML 画布:用数字替换形状 (ctx)

问题描述

所以我有以下使用形状的 HTML/JS 画布波浪动画:

https://codepen.io/baidoc/pen/yLBgppw

我正在尝试通过二进制代码替换圆形。最后,它看起来像一波二进制代码(01010101),而不是一波形状。

实现这一目标的最佳方法是什么?绘制自定义 SVG 还是通过 CTX FillText?

function drawParticle(particle, canvas, ctx) {
  canvas = document.getElementById('binary-canvas');
  const vh = canvas.height / 100;

  ctx.fillStyle = particle.colour;
  ctx.beginPath();
  ctx.ellipse(
    particle.x * canvas.width,
    particle.y * vh + (canvas.height / 2),
    particle.diameter * vh,
    particle.diameter * vh,
    0,
    0,
    2 * Math.PI
  );
  ctx.fill();
}

标签: javascripthtmlcanvas2d

解决方案


这里最好的可能是用来drawImage()渲染你的文本。

当然你可以使用fillText,但这是一个非常慢的方法,所以你调用的越少,你的性能就会越好,对于粒子系统来说,性能很重要。

所以首先,准备两张画布,一张画有“0”,另一张画有“1”。
然后,当您初始化粒子时,您只需要存储它们将引用的这些画布中的哪一个。
最后,只需调用ctx.drawImage粒子存储的画布、坐标和大小:

// returns a new <canvas> with given character drawn on it
// can be used directly with drawImage
function makeCharacterCanvas( txt ) {
  const canvas = document.createElement('canvas');
  canvas.width =  canvas.height = 50;
  const ctx = canvas.getContext('2d');
  ctx.fillStyle = 'white';
  ctx.font = "50px monospace";
  ctx.textAlign = "center";
  ctx.textBaseLine = "bottom";
  ctx.fillText(txt, canvas.width / 2, canvas.height);
  return canvas;
}
const chars = [makeCharacterCanvas('0'), makeCharacterCanvas('1')];

// OP's code below

// modified version of random-normal
function normalPool(o){var r=0;do{var a=Math.round(normal({mean:o.mean,dev:o.dev}));if(a<o.pool.length&&a>=0)return o.pool[a];r++}while(r<100)}function randomNormal(o){if(o=Object.assign({mean:0,dev:1,pool:[]},o),Array.isArray(o.pool)&&o.pool.length>0)return normalPool(o);var r,a,n,e,l=o.mean,t=o.dev;do{r=(a=2*Math.random()-1)*a+(n=2*Math.random()-1)*n}while(r>=1);return e=a*Math.sqrt(-2*Math.log(r)/r),t*e+l}

const NUM_PARTICLES = 500;
const PARTICLE_SIZE = 1.5; // View heights
const SPEED = 30000; // Milliseconds

let particles = [];

function rand(low, high) {
  return Math.random() * (high - low) + low;
}

function createParticle(canvas) {
  return {
    x: -2,
    y: -2,
    diameter: Math.max(0, randomNormal({ mean: PARTICLE_SIZE, dev: PARTICLE_SIZE / 2 })),
    duration: randomNormal({ mean: SPEED, dev: SPEED * 0.1 }),
    amplitude: randomNormal({ mean: 16, dev: 2 }),
    offsetY: randomNormal({ mean: 0, dev: 10 }),
    arc: Math.PI * 2,
    startTime: performance.now() - rand(0, SPEED),
// [edit] 
    // store which character this particle will hold
    char: chars[+(Math.random() > .5)],
    // We lost colour, but still have opacity
    opacity: rand(0, 1)
  }
}

function moveParticle(particle, canvas, time) {
  const progress = ((time - particle.startTime) % particle.duration) / particle.duration;
  return {
    ...particle,
    x: progress,
    y: ((Math.sin(progress * particle.arc) * particle.amplitude) + particle.offsetY),
  };
}

function drawParticle(particle, canvas, ctx) {
  canvas = document.getElementById('binary-canvas');
  const vh = canvas.height / 100;
// [edit]
  // set opacity
  ctx.globalAlpha = particle.opacity;
  // draw the corresponding <canvas>
  ctx.drawImage(
    particle.char,
    particle.x * canvas.width,
    particle.y * vh + (canvas.height / 2),
    particle.diameter * vh * 2,
    particle.diameter * vh * 2
  )
}

function draw(time, canvas, ctx) {
  // Move particles
  particles.forEach((particle, index) => {
    particles[index] = moveParticle(particle, canvas, time);
  })

  // Clear the canvas
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // Draw the particles
  particles.forEach((particle) => {
    drawParticle(particle, canvas, ctx);
  })

  // Schedule next frame
  requestAnimationFrame((time) => draw(time, canvas, ctx));
}

function initializeCanvas() {
  let canvas = document.getElementById('binary-canvas');
  canvas.width = canvas.offsetWidth * window.devicePixelRatio;
  canvas.height = canvas.offsetHeight * window.devicePixelRatio;
  let ctx = canvas.getContext("2d");

  window.addEventListener('resize', () => {
    canvas.width = canvas.offsetWidth * window.devicePixelRatio;
    canvas.height = canvas.offsetHeight * window.devicePixelRatio;
    ctx = canvas.getContext("2d");
  })

  return [canvas, ctx];
}

function startAnimation() {
  const [canvas, ctx] = initializeCanvas();

  // Create a bunch of particles
  for (let i = 0; i < NUM_PARTICLES; i++) {
    particles.push(createParticle(canvas));
  }
  
  requestAnimationFrame((time) => draw(time, canvas, ctx));
};

// Start animation when document is loaded
(function () {
  if (document.readystate !== 'loading') {
    startAnimation();
  } else {
    document.addEventListener('DOMContentLoaded', () => {
      startAnimation();
    })
  }
}());
html, body {
  background:#111830;
  background-image: url("https://cl.profi-homepage.de/wp-content/uploads/2019/07/bg_nur_highlight-1.png");
  background-repeat: no-repeat;
  background-position: center center;
  background-attachment: scroll;
  background-size: contain;
  margin: 0;
}

#binary-canvas {
  width: 100%;
  height: 100vh;
  vertical-align: middle;
}
<canvas id="binary-canvas"></canvas>

一个缺点是你失去了一些颜色的随机性,但考虑到你原始片段中的那些,我假设不透明度就足够了。


推荐阅读