# Detecting Browser Zoom Changes in JavaScript by [ kirupa](https://www.kirupa.com/me/index.htm) | filed under [JavaScript 101](https://www.kirupa.com/javascript/learn_javascript.htm) Whenever our browser resizes, a **resize** event gets fired. This is your friendly [run-of-the-mill DOM event](https://www.kirupa.com/html5/javascript_events.htm) that we can detect and react to using JavaScript. Now, when you and I think about resizing, we probably think of our browser’s dimensions actually changing: ![](../canvas/images/resizes_grid_200.png) As it turns out, there is another seemingly unrelated activity that also fires a **resize** event. That activity is ***zooming***: ![](../canvas/images/zoom_value_200.png) Here is the question. If we want to specifically ***only*** detect a zoom action, how can we do that? In this article, let’s figure out how to do that. Onwards! ## Zooming is Sorta Kinda Like Resizing Let’s get one thing out of the way up front. When we zoom in or zoom out, there is no dedicated zoom event that fires. I repeat. There is no **zoom** event: ![](../canvas/images/xU9TT471DTGJq.gif) Instead, what gets fired is a **resize** event. This sorta makes sense when look into what is happening at the page level. Whenever we zoom in or zoom out, the available width and height of our page changes. That UI elements also get bigger or smaller is a detail that gets clubbed in as merely being a resize activity. All hope isn’t lost, though. There is a way we can detect a browser zoom. Take a look at the following [live example](https://www.kirupa.com/javascript/examples/zoom_detect.htm): ![](../canvas/images/zooming_example.gif) As we zoom in and out, the page detects the zoom and updates the current zoom value accordingly. In the following sections, we'll look at the logic behind how we are able to detect a browser (or pinch) zoom. This is going to be a hoot! ## Checking the Viewport and Browser Size When we look at a typical web page, there are two sizes at play: ![](../canvas/images/browser_viewport_1_200.png) The two sizes are: 1. The **browser size** (referenced by window.outerWidth and window.outerHeight) measures the total size of the browser window in pixels, **including** all browser UI elements like the window frame, toolbars, scrollbars, etc. This represents the full area of the browser window as you and I see it on our screen. 1. The **viewport size** (referenced by window.innerWidth and window.innerHeight ) measures the size of the web page's visible content area in pixels. This is the actual space available for our content, and it **does not include** the browser's UI elements like scrollbars, toolbars, etc. When we zoom in or zoom out, the browser size never changes. It remains at whatever dimensions we have originally sized it at. What does change is our viewport size: ![](../canvas/images/zoom_viewport_browser_200.png) When we zoom in, our viewport size shrinks, and things get cramped. When we zoom out, our viewport size gets larger as there is now more space to arrange things. **This relationship between the browser size and viewport size is an important detail that is core to how we implement our zoom detection logic.** If we detect a situation where the browser size remains the same but the viewport size changes, we can safely assume a zoom operation has taken place. If we turn all of this explanation into a working example, we’ll have the following: ```js Detecting Zoom

100%

``` Create a new document and paste all of the above HTML, CSS, and JavaScript into a new document. Then, preview your changes in the browser. When you load this page and zoom in or zoom out, you’ll see the current zoom value update to reflect the latest zoom value: ![](../canvas/images/zoom_example.png) This zoom detection example should work identically in Chrome and Safari. That this works in Safari is pretty neat, for a lot of the approaches I saw online don’t support Safari. This example doesn't work consistently in Firefox, so do note this limitation. ## Understanding the Code Before we call it a day, let’s take a look at both how to use our code and understand why it works the way it does. ### Using this Code The way to use this code is to make sure the `ZoomDetector` class is added to your document: ```js /** * ZoomDetector class - Monitors and reports browser zoom level changes * Handles both browser zoom (Ctrl+/- or Cmd+/-) and pinch-to-zoom gestures */ class ZoomDetector { /** * Initialize the detector with initial window measurements * Sets up event listeners for zoom detection */ constructor() { // Store initial window width for comparison this.lastWidth = window.innerWidth; // Calculate and store initial zoom level this.lastScale = this.getZoomLevel(); // Set up event listeners for zoom detection this.setupListeners(); } /** * Calculate the current zoom level as a percentage * Uses the ratio of outer window width to inner window width * @returns {number} Zoom level as a percentage (e.g., 100 for 100%) */ getZoomLevel() { return Math.round(window.outerWidth / window.innerWidth * 100); } /** * Set up event listeners for different types of zoom events * Handles both browser zoom and pinch-to-zoom gestures */ setupListeners() { // Listen for browser window resize events (triggered by browser zoom) window.addEventListener('resize', () => { // Use requestAnimationFrame to optimize performance // Ensures zoom check happens during the next frame render requestAnimationFrame(() => this.checkZoom()); }); // Handle pinch-to-zoom events using visualViewport API if available if (window.visualViewport) { window.visualViewport.addEventListener('resize', () => { // Only process if window width hasn't changed // This prevents duplicate events when both viewport and window resize if (window.innerWidth === this.lastWidth) { this.checkZoom(); } }); } } /** * Check if zoom level has changed and dispatch appropriate events * Determines zoom direction and triggers custom zoom event */ checkZoom() { // Get current measurements const currentScale = this.getZoomLevel(); const currentWidth = window.innerWidth; // Only proceed if there's an actual change in zoom or window size if (currentScale !== this.lastScale || currentWidth !== this.lastWidth) { // Determine if user is zooming in or out const direction = currentScale > this.lastScale ? 'in' : 'out'; console.log(`Zoom ${direction} detected: ${currentScale}%`); // Create and dispatch custom zoom event with detailed information window.dispatchEvent(new CustomEvent('zoom', { detail: { oldScale: this.lastScale, // Previous zoom level newScale: currentScale, // New zoom level direction: direction, // Zoom direction ('in' or 'out') isWindowResize: currentWidth !== this.lastWidth // Whether window size changed } })); // Update stored values for next comparison this.lastScale = currentScale; this.lastWidth = currentWidth; } } } ``` Once you have added this code, all that remains is to initialize it and listen for a **zoom** event: ```js // Initialize zoom detector const zoomDetector = new ZoomDetector(); // Set up listener for custom zoom events window.addEventListener('zoom', (e) => { // Optionally extract zoom details from event const { oldScale, newScale, direction, isWindowResize } = e.detail; // // Your code to react to zoom activities! // }); ``` The [full example](https://www.kirupa.com/javascript/examples/zoom_detect.htm) from earlier shows all of this code (and some additional code) that shows our zoom detection code in action. ### How this Code Works If we take a 20,000 ft view, the code detects when someone zooms in or out of their browser by using the browser size and viewport size logic we described earlier. When a zoom activity is detected, our code fires a custom **zoom** event that other code across our app can listen to. The entirety of this logic lives in the `ZoomDetector` class where our `constructor` kicks things off: ```js constructor() { // Store initial window width for comparison this.lastWidth = window.innerWidth; // Calculate and store initial zoom level this.lastScale = this.getZoomLevel(); // Set up event listeners for zoom detection this.setupListeners(); } ``` The `getZoomLevel` function gets the current scale value by taking the ratio of the browser width and the viewport width: ```js getZoomLevel() { return Math.round(window.outerWidth / window.innerWidth * 100); } ``` For example, if our browser window is 1000 pixels wide and our content area is 800 pixels wide, the zoom level would be: (1000/800) * 100 = 125, meaning we’re zoomed in by 125%. We have no taken care of the initial state of our page. To detect when zooms happen after the page loads, either via using the Ctrl/Cmd and +/- keys or via a pinch gesture, we have our `setupListeners` method: ```js setupListeners() { // Listen for browser window resize events (triggered by browser zoom) window.addEventListener('resize', () => { // Use requestAnimationFrame to optimize performance // Ensures zoom check happens during the next frame render requestAnimationFrame(() => this.checkZoom()); }); // Handle pinch-to-zoom events using visualViewport API if available if (window.visualViewport) { window.visualViewport.addEventListener('resize', () => { // Only process if window width hasn't changed // This prevents duplicate events when both viewport and window resize if (window.innerWidth === this.lastWidth) { this.checkZoom(); } }); } } ``` We listen for the **resize** event in both zoom cases and call the `checkZoom` method, where the bulk of our logic for differentiating between a regular old resize and the “what we care about” zoom activity takes place: ```js checkZoom() { // Get current measurements const currentScale = this.getZoomLevel(); const currentWidth = window.innerWidth; // Only proceed if there's an actual change in zoom or window size if (currentScale !== this.lastScale || currentWidth !== this.lastWidth) { // Determine if user is zooming in or out const direction = currentScale > this.lastScale ? 'in' : 'out'; console.log(`Zoom ${direction} detected: ${currentScale}%`); // Create and dispatch custom zoom event with detailed information window.dispatchEvent(new CustomEvent('zoom', { detail: { oldScale: this.lastScale, // Previous zoom level newScale: currentScale, // New zoom level direction: direction, // Zoom direction ('in' or 'out') isWindowResize: currentWidth !== this.lastWidth // Whether window size changed } })); // Update stored values for next comparison this.lastScale = currentScale; this.lastWidth = currentWidth; } } ``` Take a few moments to walk through how this code works. When we detect that a zoom activity has indeed taken place, we fire a custom zoom event: ```js // Create and dispatch custom zoom event with detailed information window.dispatchEvent(new CustomEvent('zoom', { detail: { oldScale: this.lastScale, // Previous zoom level newScale: currentScale, // New zoom level direction: direction, // Zoom direction ('in' or 'out') isWindowResize: currentWidth !== this.lastWidth // Whether window size changed } })); ``` When this **zoom** event is fired, all event listeners throughout our app that are listening for this event will get triggered. This is the final task that all of the code we’ve seen build up to! ## Conclusion You may be wondering when you'll ever need to know when a zoom event is taking place. In situations where you want to preserve the exact visual output as much as possible, such as in many 2D or WegGL-based canvas scenarios, it will be very handy to detect zooms and handle its effects. What we saw here is one approach where we can use the browser size and viewport size as a proxy metric to detect when changes happen and determine if those changes were in fact caused by a zoom operation.