skip to Main Content

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>

2

Answers


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

    Login or Signup to reply.
  2. Draw relative to origin

    Scale objects from their local origin.

    A box 20 by 20 pxs has 4 corners that are relative to a point {x: 0, y: 0}

    The top left corner is at {x: -10, y: -10}
    The bottom right corner is at {x: 10, y: 10}

    You can then draw the box by setting where its center point will be on the canvas.

    Scaling or changing the size of the box moves all the points towards or away from the center point.

    This can greatly reduce the complexity of the source code (as you can see in the example below).

    Example

    Example below rewrites from ground up.

    fillSquare draws a square centered on a point.

    Note The example uses the time as given by the requestAnimationFrame callbacks’ argument to control the rate of the animation, one pulse per second.

    const tag = (name, opts = {}) => Object.assign(document.createElement(name), opts);
    const qry = (qStr, root = document) => root.querySelector(qStr);
    const append = (el, ...sibs) => sibs.reduce((el, sib) => (el.appendChild(sib), el), el);
    const P2 = (x, y) => ({x, y});
    
    const MS_2_RAD = 0.001 * Math.PI * 2;  // one pulse per second
    const SIZE = 45;
    const pos = P2(SIZE * 0.5, SIZE * 0.5); 
    
    const canvas = tag("canvas", {width: SIZE, height: SIZE});
    const ctx = canvas.getContext("2d");
    append(qry(".wrap"), canvas);
    requestAnimationFrame(mainLoop);
    function fillSquare(ctx, pos, size, color = "#000") {
        ctx.fillStyle = color;
        ctx.setTransform(1, 0, 0, 1, pos.x, pos.y);
        ctx.fillRect(size * -0.5, size * -0.5, size, size);
    }
    function mainLoop(time){
        ctx.setTransform(1, 0, 0, 1, 0, 0);  // reset transform before clear
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        fillSquare(ctx, pos, SIZE);
        fillSquare(ctx, pos, (Math.sin(time * MS_2_RAD) * 0.3 + 0.7) * SIZE, "#F00");
        requestAnimationFrame(mainLoop);
    }
    <div class="wrap">
    
    </div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search