Search code examples
javascripthtml5-canvas

HTML Canvas, pulse animation from center position


I have created pulse animation using html canvas. There is two rectangles. Rectangle's positions is updated dynamically. The smaller rectangle has pulse animation. I have problem calculate it's center position value. Without scaling I get correct position. But when scaling position is incorrect. How to calculate scaled X and Y?

const canvas = document.createElement("canvas");
canvas.setAttribute("width", "500px");
canvas.setAttribute("height", "500px");
const ctx = canvas.getContext("2d");
const wrapper = document.getElementsByClassName("wrap")[0];
wrapper.appendChild(canvas);
let scale = 1;
let angle = 0;
const blockSize = 45; // Rectangle size
const pos = {x: 2, y: 1}; // Rectangle position


function anim(){
   ctx.clearRect(0, 0, canvas.width, canvas.height);
   ctx.setTransform(1, 0, 0, 1, 0, 0);
   draw(ctx, pos)
   update();
   requestAnimationFrame(anim);
}
anim();

function draw(ctx, position){
    ctx.fillStyle = "#000";
  ctx.fillRect((position.x * blockSize - blockSize), (position.y * blockSize - blockSize), blockSize, blockSize);

  // Smaller rectangle with pulse
  ctx.fillStyle = "red";
  const centerX = (position.x * blockSize - blockSize) / 2;
  const centerY = (position.y * blockSize - blockSize) / 2;
  const scaledX = centerX - (centerX * scale); 
  const scaledY = centerY - (centerY * scale);
  ctx.setTransform(scale, 0, 0, scale, centerX, scaledY);
  const recSize = 30;
  const x = (position.x * blockSize - blockSize) + ((blockSize - recSize) / 2);
  const y = (position.y * blockSize - blockSize) + ((blockSize - recSize) / 2);
  ctx.fillRect(x, y, recSize, recSize);
  ctx.setTransform(1, 0, 0, 1, 0, 0);
}

function update(){
    angle += Math.PI / 140;
  scale = 0.5 + Math.abs(Math.cos(angle));
}
<div class="wrap">

</div>


Solution

  • const canvas = document.createElement("canvas");
    canvas.setAttribute("width", "500px");
    canvas.setAttribute("height", "500px");
    const ctx = canvas.getContext("2d");
    const wrapper = document.getElementsByClassName("wrap")[0];
    wrapper.appendChild(canvas);
    let scale = 1;
    let angle = 0;
    const blockSize = 45; // Rectangle size
    const pos = {x: 2, y: 1}; // Rectangle position
    
    
    function anim(){
       ctx.clearRect(0, 0, canvas.width, canvas.height);
       ctx.setTransform(1, 0, 0, 1, 0, 0);
       draw(ctx, pos)
       update();
       requestAnimationFrame(anim);
    }
    anim();
    
    function draw(ctx, position){
        // Draw the larger black rectangle
        ctx.fillStyle = "#000";
        const largeRectX = position.x * blockSize - blockSize;
        const largeRectY = position.y * blockSize - blockSize;
        ctx.fillRect(largeRectX, largeRectY, blockSize, blockSize);
    
        // Calculate the center of the larger rectangle
        const centerX = largeRectX + blockSize / 2;
        const centerY = largeRectY + blockSize / 2;
    
        // Save the current context state
        ctx.save();
    
        // Translate to the center, scale, then translate back
        ctx.translate(centerX, centerY);
        ctx.scale(scale, scale);
        ctx.translate(-centerX, -centerY);
    
        // Draw the smaller red rectangle
        ctx.fillStyle = "red";
        const recSize = 30;
        const smallRectX = largeRectX + (blockSize - recSize) / 2;
        const smallRectY = largeRectY + (blockSize - recSize) / 2;
        ctx.fillRect(smallRectX, smallRectY, recSize, recSize);
    
        // Restore the context state
        ctx.restore();
    }
    
    function update(){
        angle += Math.PI / 140;
      scale = 0.5 + Math.abs(Math.cos(angle));
    }
    <div class="wrap">
    
    </div>

    codepen

    Here's what this code does:

    It draws the larger black rectangle as before. It calculates the center of the larger rectangle. It saves the current context state. It applies the scaling transformation:

    First, it translates to the center of the larger rectangle. Then it applies the scaling. Finally, it translates back.

    It draws the smaller red rectangle. It restores the context state.