Tips for creating a scale animation using HTML5 Canvas

I am currently developing a canvas whiteboard tool and I have reached the stage where I am focusing on implementing the Zoom In and Zoom Out feature.

While the functionality is working fine, I would like to enhance it with smooth animations for scaling. However, the current scaling happens too quickly that the effect is not noticeable. Whenever I click on the + or - button, the scaling occurs instantly.

let scaleAmount = 1.00;

function scaleUpdate()
{
    ctx.setTransform(1, 0, 0, 1, 0, 0);
    ctx.scale(scaleAmount, scaleAmount)   
    
    scaleSize.innerText = parseInt(parseFloat(scaleAmount.toFixed(2))*100)+"%";

    stageUpdate();  
   
}

function scale(direction)
{
    let scaleSize = document.getElementById("scaleSize");
    
    if(direction=="zoomIn")
    { 
        for(let i = 0; i<10; i++)
        {
            scaleAmount += 0.01;
            scaleAmount = parseFloat(scaleAmount.toFixed(2));
            dx += scaleAmount;
            dy += scaleAmount;   
            
            scaleUpdate();
        }
    }
    if(direction=="zoomOut")
    {
        for(let i = 0; i<10; i++)
        {
            scaleAmount -=0.01;
            scaleAmount = parseFloat(scaleAmount.toFixed(2));
            dx -= scaleAmount;
            dy -= scaleAmount;
            scaleUpdate();
        }
    }
}

dx and dy represent screen offsets.

Can anyone assist me in addressing this issue?

Thank you, Frank

Answer №1

When updating visual properties such as scale within a for-loop, the changes happen almost instantaneously - not literally all at once but certainly much faster than the human eye can perceive. The screen does not refresh with each iteration of the loop; instead, it appears to update only before and after calling the scale() function.

To make these changes visible over time, you must implement them gradually. For instance, starting at time 1.0, set the scale to 1.0, at 1.1, set it to 1.01, at 1.2, set it to 1.02, and so on until reaching the desired scale. This can be achieved using either the setInterval() or requestAnimationFrame() functions. setInterval requires two arguments: the function to call and the time (in milliseconds) at which to call this function. On the other hand, requestAnimationFrame attempts to call the specified function at the display's refresh rate - approximately 60 times per second on a 60Hz display.

To optimize your example using the requestAnimationFrame function:

let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");

document.getElementById("scaleUp").onclick = () => {
  cancelAnimationFrame(animationID);
  scale("zoomIn");
}
document.getElementById("scaleDown").onclick = () => {
  cancelAnimationFrame(animationID);
  scale("zoomOut");
}

let scaleAmount = 1.0;
let animationID;

function scale(direction) {
  let animationReady = false;
  let targetScale;
  if (direction == "zoomIn") {
    scaleAmount += 0.01;
    targetScale = 2;
    animationReady = scaleAmount >= targetScale;
  }
  if (direction == "zoomOut") {
    scaleAmount -= 0.01;
    targetScale = 1;
    animationReady = scaleAmount <= targetScale;
  }

  if (animationReady) {
    scaleAmount = targetScale;
  } else {
    animationID = requestAnimationFrame(() => {
      scale(direction);
    });
  }
  stageUpdate();
}

function stageUpdate() {
  ctx.setTransform(1, 0, 0, 1, 0, 0);
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.translate(canvas.width / 2, canvas.height / 2);
  ctx.scale(scaleAmount, scaleAmount);
  ctx.beginPath();
  ctx.arc(0, 0, 45, 0, 2 * Math.PI);
  ctx.closePath();
  ctx.stroke();
}

stageUpdate();
<button id="scaleUp" type="button">Scale +</button><button id="scaleDown" type="button">Scale -</button><br><canvas id="canvas" width="320" height="200"></canvas>

Answer №2

@uniqueUser Your insight was incredibly valuable, much appreciated.

After incorporating your suggestions, my final code now appears as follows:

let scaleAmount = 1.00;
let scaleAnimation;
let animationReady = false;
let scalePercent =  parseInt(scaleSize.innerText.replace("%",""));
function scaleUpdate()
{
    ctx.setTransform(1, 0, 0, 1, 0, 0);
    ctx.scale(scaleAmount, scaleAmount)   
    
    scaleSize.innerText = parseInt(parseFloat(scaleAmount.toFixed(1))*100)+"%";

    stageUpdate();  
   
}

function scale(direction)
{
    
    
    let targetScale ;

    if(direction=="zoomIn")
    { 
            targetScale = scalePercent / 100 + 0.1;
            
            scaleAmount += 0.01;
        
            animationReady = scaleAmount > targetScale;
            targetScale= parseFloat(targetScale.toFixed(3));
            scaleAmount = parseFloat(scaleAmount.toFixed(3));
            dx += scaleAmount;
            dy += scaleAmount;   
    }
    if(direction=="zoomOut")
    {
            targetScale = scalePercent / 100 - 0.1;

            scaleAmount -= 0.01;
           
            animationReady = scaleAmount < targetScale;
            targetScale= parseFloat(targetScale.toFixed(3));
            scaleAmount = parseFloat(scaleAmount.toFixed(3));
            dx -= scaleAmount;
            dy -= scaleAmount;
    }
 
    if (animationReady) {
        scaleAmount = targetScale;
        scalePercent =  parseInt(scaleSize.innerText.replace("%",""));
    } else {
        scaleAnimation = requestAnimationFrame(() => {
            scaleUpdate();
            scale(direction);
         });
    }
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Encountered a runtime error in NgRx 7.4.0: "Uncaught TypeError: ctor is not a

I'm facing difficulties trying to figure out why I can't register my effects with NgRx version 7.4.0. Despite simplifying my effects class in search of a solution, I keep encountering the following error: main.79a79285b0ad5f8b4e8a.js:33529 Uncau ...

Encountering errors like "Cannot find element #app" and "TypeError: Cannot read property 'matched' of undefined" typically happens when using vue-router

Recently, I decided to dive into Vue.js as a way to revamp an existing frontend app that was originally built using Scala Play. My goal is to explore the world of component-based web design and enhance my skills in this area. Initially, everything seemed ...

Interacting with the DOM of an iFrame from a separate window using Javascript

My main webpage is hosted on "DomainA" and it contains an iFrame sourced from "DomainB". Within this iFrame, there is a click event that opens a new window, also sourced from "DomainB". I am attempting to update an input field within the iFrame from the n ...

Updating parent component's scrollHeight of DOM element based on child component in Next.js

I recently encountered an issue with nested accordions. In my project, I have implemented accordions within other accordions, but ran into a problem when the inner accordion expanded and affected the size of the parent accordion. Here's what's ha ...

Creating a regular expression to match special characters using JavaScript

I'm making an attempt to verify certain characters in my code. If the input field contains specific characters for each character, it will return true; otherwise, it will return false. function isChar(value) { //Creating a regex pattern to permit ...

Tips on preserving type safety after compiling TypeScript to JavaScript

TS code : function myFunction(value:number) { console.log(value); } JS code, post-compilation: function myFunction(value) { console.log(value); } Are there methods to uphold type safety even after the conversion from TypeScript to JavaScript? ...

Issue with Ajax reload functions malfunctioning

Embarking on a new journey, I am diving headfirst into the world of web development. As an artist and writer, I have recently delved into the realm of creating a project that integrates a cms, project manager, and database front-end using php, mysql, and j ...

The value of a variable decreases upon being utilized in an Ajax API call

My tempid variable seems to lose some of its values when passed into the second API call. Despite logging the variable to console (console.log(tempid)) where it appears fine, once placed in the API call it only retains some of its value. I'm uncertain ...

Establish a global variable within a TypeScript file

I am currently facing an issue where I need to add an event to the browser inside the CheckSocialMedia function. However, it keeps saying that it could not find the name. So, my question is how do I make the 'browser' variable global in the .ts ...

Encountering issues while attempting to execute node-sass using npm

Currently, I'm attempting to execute node-sass using npm. Displayed below is my package.json: { "name": "my-project", "version": "1.0.0", "description": "Website", "main": "index.js", "scripts": { "sass": "node-sass -w scss/ -o dist ...

Error occurred while trying to authenticate the user "root" with the password in Linux using NodeJS, Express, and PostgreSQL

Update - Hurrah! It appears I neglected to consult the manual. Following the guidelines exactly for the environmental variables seems to be necessary. Corrected code: # PostgreSQL Database Information PGDATABASE_TEST = user_db PGDATABASE = user_db PGUSER ...

When jQuery fails to detach() due to the presence of an input tag

I have a situation where I am trying to rearrange elements within a table. Everything works fine, until I introduce a tag, which triggers this error message:</p> <pre><code>Error: this.visualElement is undefined Source File: http://192. ...

Tips for Showing Websocket Response On Web Browser Using Node.js

Just starting out with NodeJS and here's my code snippet: const webSocket= require('ws'); const express = require('express'); const app=express(); Var url = "wss://stream.binance.com:9443/BTCUSDT@trade`" const ws = new webS ...

Manipulating DropDownList Attributes in ASP.NET using JavaScript

I am facing an issue with populating a Dropdownlist control on my ASCX page. <asp:DropDownList ID="demoddl" runat="server" onchange="apply(this.options[this.selectedIndex].value,event)" onclick="borderColorChange(this.id, 'Click')" onblur="bo ...

What is the best way to implement a dialog box that displays a website within it, all while keeping my SharePoint site running in the background?

For a while now, I've been working on SharePoint Online and trying to troubleshoot an issue without success. That's why I'm considering starting over with a specific section of my SP site from scratch. My current project involves creating a ...

Limiting the style of an input element

How can I mask the input field within an <input type="text" /> tag to restrict the user to a specific format of [].[], with any number of characters allowed between the brackets? For example: "[Analysis].[Analysis]" or another instance: "[Analysi ...

transferring information between pages in nextjs

Currently in the process of developing a website, specifically working on a registration page for user sign-ups. My main challenge at the moment is validating email addresses without using Links. I need to redirect users to a new page where they can see if ...

How to achieve horizontal auto-scrolling in an image gallery with jQuery?

Hey there, I'm currently working on an Image Gallery project. I have arranged thumbnails horizontally in a div below the main images. Take a look at this snapshot img. My goal is to have the thumbnails scroll along with the main pictures as the user ...

Number each element in sequence

Looking to give sequential numbering to elements by iterating through them. For instance, if there are 6 input elements, the goal is to update their names correspondingly like "name=input1", "name=input2", and so on. This involves using a for loop to reas ...

The Gulp task is stuck in an endless cycle

I've set up a gulp task to copy all HTML files from a source folder to a destination folder. HTML Gulp Task var gulp = require('gulp'); module.exports = function() { return gulp.src('./client2/angularts/**/*.html') .pipe( ...