Tips for using JavaScript to magnify a specific point on a webpage

As I work on my web project, I am trying to achieve the functionality of zooming in a div element centered around the mouse position while using the mouse wheel. I came across a helpful answer by @Tatarize on Zoom in on a point (using scale and translate), but I am having trouble implementing it accurately. The current issue is that the zoom and translation are not working as expected around the mouse position. Can anyone provide assistance?

window.onload = function() {
    const STEP = 0.05;
    const MAX_SCALE = 10;
    const MIN_SCALE = 0.01;

    const red = document.getElementById('red');
    const yellow = red.parentNode;

    let scale = 1;

    yellow.onmousewheel = function (event) {
        event.preventDefault();
        
        let mouseX = event.clientX - yellow.offsetLeft - red.offsetLeft;
        let mouseY = event.clientY - yellow.offsetTop - red.offsetTop;

        const factor = event.wheelDelta / 120;

        const oldScale = scale;
        scale = scale + STEP * factor;
        scale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, scale));

        const scaleChanged = scale - oldScale;
        const offsetX = -(mouseX * scaleChanged);
        const offsetY = -(mouseY * scaleChanged);

        console.log(offsetX, offsetY);

        red.style.transform = 'translate(' + offsetX + 'px, ' + offsetY + 'px)' + 'scale(' + scale + ')';
    }
}
.yellow {
    background-color: yellow;
    width: 400px;
    height: 200px;
}

.red {
    background-color: red;
    width: 100px;
    height: 50px;
}
<div class="yellow">
    <div id="red" class="red"></div>
</div>

Answer №1

Wow, I can't believe I actually accomplished it!

window.onload = () => {
    const STEP = 0.99;
    const MAX_SCALE = 5;
    const MIN_SCALE = 0.01;

    const red = document.getElementById("red");
    const yellow = red.parentNode;

    let scale = 1;

    const rect = red.getBoundingClientRect();
    const originCenterX = rect.x + rect.width / 2;
    const originCenterY = rect.y + rect.height / 2;

    yellow.onwheel = (event) => {
        event.preventDefault();

        const factor = event.deltaY;

        // If current scale is equal to or greater than MAX_SCALE, but you're still zoom in it, then return;
        // If current scale is equal to or smaller than MIN_SCALE, but you're still zoom out it, then return;
        // Can not use Math.max and Math.min here, think about it.
        if ((scale >= MAX_SCALE && factor < 0) || (scale <= MIN_SCALE && factor > 0)) return;
        
        const scaleChanged = Math.pow(STEP, factor);
        scale *= scaleChanged;

        const rect = red.getBoundingClientRect();
        const currentCenterX = rect.x + rect.width / 2;
        const currentCenterY = rect.y + rect.height / 2;

        const mousePosToCurrentCenterDistanceX = event.clientX - currentCenterX;
        const mousePosToCurrentCenterDistanceY = event.clientY - currentCenterY;

        const newCenterX = currentCenterX + mousePosToCurrentCenterDistanceX * (1 - scaleChanged);
        const newCenterY = currentCenterY + mousePosToCurrentCenterDistanceY * (1 - scaleChanged);

        // All we are doing above is: getting the target center, then calculate the offset from origin center.
        const offsetX = newCenterX - originCenterX;
        const offsetY = newCenterY - originCenterY;

        // !!! Both translate and scale are relative to the original position and scale, not to the current.
        red.style.transform = 'translate(' + offsetX + 'px, ' + offsetY + 'px)' + 'scale(' + scale + ')';
    }
}
.yellow {
  background-color: yellow;
  width: 200px;
  height: 100px;

  margin-left: 50px;
  margin-top: 50px;

  position: absolute;
}

.red {
  background-color: red;
  width: 200px;
  height: 100px;

  position: absolute;
}
<div class="yellow">
    <div id="red" class="red"></div>
</div>

Answer №2

The usage of .onmousewheel has been deprecated, and it is recommended to use .onwheel instead.

Additionally, the onwheel event does not contain the property wheelDelta. It is advised to use deltaY instead.

Answer №3

My code provided is intended to adjust the viewbox based on a specific zoom point. The current implementation is shifting the rectangle using calculations that are not suitable for this scenario.

The goal is to move the zoom area in relation to the scale change. Instead of altering the position and orientation of a rectangle, the focus should be on simulating the new position of the red rectangle as if the yellow rectangle acted as a viewport. This entails zooming in at a translateX translateY position determined by a particular scale factor. The next step is to translate the zoom point value into the appropriate scene space and then reposition the red rectangle accordingly.

Below is the code with some corrections, although there are missing elements. The main issue lies in the failure to maintain the translateX translateY values. By overwriting these values, the code only preserves the zoom aspect, disrupting the expected translateX translateY behavior when dealing with a relative viewport offset.

In a functional implementation, zooming in on the rectangle should result in the red rectangle filling the entire scene space.

window.onload = function() {
    const STEP = 0.05;
    const MAX_SCALE = 10;
    const MIN_SCALE = 0.01;

    const red = document.getElementById('red');
    const yellow = document.getElementById('yellow');
    const svgArea = document.getElementById('svgArea');

    let viewportTranslateX = 0;
    let viewportTranslateY = 0;
    let viewportScale = 1;

    svgArea.onwheel = function (event) {
        event.preventDefault();
        console.log("mouse coords", event.clientX, event.clientY);
         
        let zoompointX = (event.clientX + (viewportTranslateX / viewportScale)) * viewportScale;
        let zoompointY = (event.clientY + (viewportTranslateY / viewportScale)) * viewportScale;
        console.log("zoom point prezoom", zoompointX, zoompointY);
        
        const factor = event.deltaY / 120;

        const oldScale = viewportScale;
        viewportScale = viewportScale * (1 + STEP * factor);
        viewportScale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, viewportScale));

        const scaleChanged = viewportScale - oldScale;
        const offsetX = -(zoompointX * scaleChanged);
        const offsetY = -(zoompointY * scaleChanged);
        console.log("scale", scaleChanged, offsetX, offsetY);
        viewportTranslateX += offsetX;
        viewportTranslateY += offsetY;

        zoompointX = (event.clientX + (viewportTranslateX / viewportScale)) * viewportScale;
        zoompointY = (event.clientY + (viewportTranslateY / viewportScale)) * viewportScale;
        console.log("zoompoint postzoom", zoompointX, zoompointY);

        var x = viewportTranslateX;
        var y = viewportTranslateY;
        var width = (svgArea.getAttribute("width") * viewportScale);
        var height = (svgArea.getAttribute("height") * viewportScale);

        svgArea.setAttribute("viewBox", x + " " + y + " " + width + " " + height);
        console.log("viewport", x, y, width, height, viewportScale);
    }
}
<svg id="svgArea" width=400 height=200 viewBox="0,0,400,200">
   <rect id="yellow" width=400 height=200 fill="yellow"/>
   <rect id="red" width=100 height=50 fill="red"/>
</svg>

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

Can you use both HTML and JSON in ROR?

The scaffolding process generates controllers that include Create/Update methods. These methods are responsible for rendering HTML as well as JSON data. While I am familiar with HTML, I have limited knowledge about JSON. Should JSON be included in this cod ...

Steer clear of encountering the "$digest already in progress" issue

A custom directive named 'myPagination' has been implemented, which encapsulates the functionality of the UI Bootstrap's pagination directive. angular.module('my-module') .directive('myPagination', ['$filter' ...

Choose all the inputs with the value attribute specified

How can I select all input textboxes in my form that have the required class and no value using jQuery? Currently, I am using: $('input[value=""]', '.required') The issue I am facing is that even when a user enters a value in the text ...

Surprising outcomes encountered when playing audio with JavaScript

https://i.sstatic.net/1jz45.png I've been diving into learning JavaScript and decided to create a simple web page. This page, when Pikachu (image) is clicked, plays an audio file. Similarly, if the string "Pikachu" is typed into the form, it should ...

Three.js experiencing issues with FBX animations running erratically

Having trouble with animations on some fbx models. When an animation lasts 20 seconds, the model remains stationary for 19 seconds and then suddenly moves within the last second or so. However, other fbx models animate correctly. The code used to run the a ...

Struggled to Find a Solution for Code Alignment

Is there a tool or software that can align different types of codes with just one click? I've tried using the Code alignment plugin in Notepad++, but it hasn't been effective. For example, when aligning HTML code using tags, I couldn't find ...

Trouble with executing asynchronous AJAX request using when() and then() functions

Here is the code that I am currently using: function accessControl(userId) { return $.ajax({ url: "userwidgets", type: "get", dataType: 'json', data: { userid: userId } }); }; var user ...

Is sendFile causing an error due to an invalid JSON format?

Whenever I try to send a file to a client for download, I encounter an exception saying that the JSON is invalid. Is there a better way to send the file, perhaps using res.download and setting the content as JSON? I want to avoid using AngularJS FileSaver ...

Tips on how to define an ajax request for the PHP section that is being managed

I recently started using ajax calls to retrieve information from php files and I have 7 elements in my html that trigger these calls. I've created 7 separate php files to handle each ajax call, like so: html part: <a style="cursor:pointer;" id="o ...

JavaScript first, middle, and last names

When working with Javascript, I have encountered a challenge. I am attempting to extract the First, Middle, and Last names from a full name input into three separate fields - Character Length, Middle Name, and Initials. At this point, I have successfull ...

What measures can be taken to block Javascript from retrieving PHP cookie information?

(Extracted from an interview) Identify the correct answers from the list below: Implement the httponly parameter when creating the cookie The user needs to disable Javascript support This setting is related to cookies in the browser Restrict access to t ...

Looking for a regular expression to require either a dollar sign ($) or a percentage symbol (%) but not

At the moment, I currently have this input field set up on an HTML page within my Angular 9 application: <input type="text" formControlName="amountToWithholdInput" onkeyup="this.value = this.value.replace(/[^0-9.%$]/, &ap ...

Managing the expiration time of a Cookie with ngx-cookie-service: Explained

Currently, I am utilizing ngx-cookie-service in my Angular application. According to the official documentation, it is mentioned that a third parameter can be added for defining the expiration time as shown below: this.cookieService.set('CookieName&ap ...

Utilizing a variable within an Angular filter: A step-by-step guide

Currently, I am in the process of experimenting with Angular. I am able to retrieve some data from the controller and successfully apply a filter when passing it as a string. However, I encounter issues when attempting to use a variable for the filter inst ...

The Vue component mistakenly saves the image to the incorrect location when the file @change event is triggered

I've encountered an issue with my image slider component. Users have the option to add an extra image to the end of the slider, but when there are multiple instances of the same slider component on a page, it always adds the image to the first compone ...

The @input function in Vue.js is currently only triggered after the user has focused out, but I need it to be called while the user is

When working with @input on an input field in Vue.js, I am facing an issue where the assigned function is only called after the user has stopped typing and the focus is out of the input field. Essentially, it is triggered on onFocusout. What I actually wan ...

Developing a Node.js system for mapping ids to sockets and back again

Managing multiple socket connections in my application is proving to be a challenge. The app functions as an HTTP server that receives posts and forwards them to a socket. When clients establish a socket connection, they send a connect message with an ID: ...

Creating a dynamic form that efficiently captures user information and automatically redirects to the appropriate web page

Sorry for the unusual question, but it was the best way I could think of asking. I have come across websites with fill-in-the-blank lines where you can input your desired information and then get redirected to another page. For example: "What are you sea ...

Compilation failure resulting from core UI build in React js

I have recently transitioned my domain to React Js and am in the process of learning. I have been working on creating an admin panel using Core UI Reactjs components, however I keep encountering an error that says "This error occurred during the build ti ...

What is the process for capturing a window screenshot using Node.js?

I am currently conducting research to discover a way to capture a screenshot of a window using Node.js. I have been attempting to achieve this with node-ffi, but I am facing some difficulties at the moment: var ffi = require('ffi'); var user32 ...