173

Is it possible to detect, using JavaScript, when the user changes the zoom in a page? I simply want to catch a "zoom" event and respond to it (similar to window.onresize event).

Thanks.

user123093
  • 2,147
  • 3
  • 16
  • 16
  • What would you change upon the zoom event? – James Jun 15 '09 at 13:02
  • 13
    I believe that the newer browsers do fire the onresize event when the page is zoomed. – epascarello Jun 15 '09 at 14:43
  • 1
    I have a similar problem, but I don't just want to know when the zoom is changed, I want to know the value of the zoom when my page loads, as well. – Nosredna Oct 21 '09 at 23:58
  • possible duplicate of [How to detect page zoom level in all modern browsers?](http://stackoverflow.com/questions/1713771/how-to-detect-page-zoom-level-in-all-modern-browsers) – John Conde Aug 05 '12 at 21:46
  • 3
    Really not a duplicate. The question here is about catching the event, not determining the zoom level. – Assaf Lavie Aug 10 '12 at 20:35
  • @epascarello FF17 on Ubuntu does not fire onresize on zoom. – HorusKol Dec 20 '12 at 02:44
  • @HorusKol Do you realize this is 3 years old? ;) – epascarello Dec 20 '12 at 03:08
  • 5
    @epascarello - yes, but it doesn't invalidate my comment – HorusKol Dec 20 '12 at 05:17
  • In latest jQuery accent, you can use `$(window).on("resize", function(){ /* do something; */ })`. Works in all modern browsers. – vulcan raven Apr 03 '13 at 10:35
  • 7
    I'd like to confirm about 'onresize' occuring in newest browsers. So far I see newset Chrome (33), FF (28), IE(11 and 11 in 9th Mode) all correctly trigger the event when you zoom in or out. – Brock Apr 08 '14 at 15:34

16 Answers16

80

There's no way to actively detect if there's a zoom. I found a good entry here on how you can attempt to implement it.

I’ve found two ways of detecting the zoom level. One way to detect zoom level changes relies on the fact that percentage values are not zoomed. A percentage value is relative to the viewport width, and thus unaffected by page zoom. If you insert two elements, one with a position in percentages, and one with the same position in pixels, they’ll move apart when the page is zoomed. Find the ratio between the positions of both elements and you’ve got the zoom level. See test case. http://web.archive.org/web/20080723161031/http://novemberborn.net/javascript/page-zoom-ff3

You could also do it using the tools of the above post. The problem is you're more or less making educated guesses on whether or not the page has zoomed. This will work better in some browsers than other.

There's no way to tell if the page is zoomed if they load your page while zoomed.

Community
  • 1
  • 1
Ian Elliott
  • 7,110
  • 5
  • 32
  • 41
  • 1
    One can check size parameters inside the onload handler for the body. Curiously, in debugging IE8 vs. 9 it appears that the onresize handler for the body is passed the document in IE8, while IE9 passes the window, as does Chrome. – Walter K Aug 08 '12 at 21:57
  • If you place the two elements at exactly same positions and the page loads while zoomed, wouldn't there be a displacement between the two elements? Wouldn't this tell if the page was already zoomed or not? – vijayant Jun 27 '17 at 15:06
  • also `document.body.offsetWidth` – Samy Bencherif Jul 05 '17 at 22:23
  • Using a method like this will also fire when a user resizes the window, so why not just use the resize event listener? In which case it is also a viable solution. – hewiefreeman Aug 19 '17 at 04:22
16

Good news everyone some people! Newer browsers will trigger a window resize event when the zoom is changed.

Ben
  • 47,286
  • 44
  • 159
  • 208
15

Lets define px_ratio as below:

px ratio = ratio of physical pixel to css px.

if any one zoom The Page, the viewport pxes (px is different from pixel ) reduces and should be fit to The screen so the ratio (physical pixel / CSS_px ) must get bigger.

but in window Resizing, screen size reduces as well as pxes. so the ratio will maintain.

zooming: trigger windows.resize event --> and change px_ratio

but

resizing: trigger windows.resize event --> doesn’t change px_ratio

//for zoom detection
px_ratio = window.devicePixelRatio || window.screen.availWidth / document.documentElement.clientWidth;

$(window).resize(function(){isZooming();});

function isZooming(){
    var newPx_ratio = window.devicePixelRatio || window.screen.availWidth / document.documentElement.clientWidth;
    if(newPx_ratio != px_ratio){
        px_ratio = newPx_ratio;
        console.log("zooming");
        return true;
    }else{
        console.log("just resizing");
        return false;
    }
}

The key point is difference between CSS PX and Physical Pixel.

https://gist.github.com/abilogos/66aba96bb0fb27ab3ed4a13245817d1e

Community
  • 1
  • 1
Abilogos
  • 3,074
  • 2
  • 11
  • 27
  • 2
    This should be the accepted answer IMO. Works flawlessly so far, thanks! Wrapped it up into a custom event if anyone is interested https://gist.github.com/souporserious/b44ea5d04c38c2e7ff32cd1912a17cd0. – souporserious Oct 15 '19 at 22:01
13

I'm using this piece of JavaScript to react to Zoom "events".
It polls the window width. (As somewhat suggested on this page (which Ian Elliott linked to): http://novemberborn.net/javascript/page-zoom-ff3 [archive])

Tested with Chrome, Firefox 3.6 and Opera, not IE.

Regards, Magnus

var zoomListeners = [];

(function(){
  // Poll the pixel width of the window; invoke zoom listeners
  // if the width has been changed.
  var lastWidth = 0;
  function pollZoomFireEvent() {
    var widthNow = jQuery(window).width();
    if (lastWidth == widthNow) return;
    lastWidth = widthNow;
    // Length changed, user must have zoomed, invoke listeners.
    for (i = zoomListeners.length - 1; i >= 0; --i) {
      zoomListeners[i]();
    }
  }
  setInterval(pollZoomFireEvent, 100);
})();
Derek 朕會功夫
  • 84,678
  • 41
  • 166
  • 228
KajMagnus
  • 10,177
  • 14
  • 69
  • 115
  • 8
    what about the false positives with window resizes? – gcb Dec 10 '10 at 19:30
  • 5
    But you could filter out those cases when you also implement an onresize handler. So the basics for a solution are here I'd say. – Stijn de Witt Nov 09 '12 at 12:54
  • @StijndeWitt After reading [this](http://www.quirksmode.org/mobile/viewports.html) I am starting to understand the proposed path, now I am working on a solution with filtering out resize events, especially on mobile. Anyone? – lowtechsun Jan 25 '17 at 00:45
  • Maybe you could use a gap value to differenciate resize and zoom ? I mean zoom will change instantly window width by > x pixels. – Sebastien Mar 23 '17 at 16:00
  • 1
    False positives are an issue, yeah. Wouldn't it eliminate 99.99% of all false positives by checking if the aspect ratio was maintained? Have the script remember the last ratio and calculate the new ratio & check if those are the same. Combine that with width being different and you should have a good base – Biepbot Von Stirling Dec 30 '17 at 12:35
6

This works for me:

        var deviceXDPI = screen.deviceXDPI;
        setInterval(function(){
            if(screen.deviceXDPI != deviceXDPI){
                deviceXDPI = screen.deviceXDPI;
                ... there was a resize ...
            }
        }, 500);

It's only needed on IE8. All the other browsers naturally generate a resize event.

Bill Keese
  • 1,593
  • 12
  • 26
3

There is a nifty plugin built from yonran that can do the detection. Here is his previously answered question on StackOverflow. It works for most of the browsers. Application is as simple as this:

window.onresize = function onresize() {
  var r = DetectZoom.ratios();
  zoomLevel.innerHTML =
    "Zoom level: " + r.zoom +
    (r.zoom !== r.devicePxPerCssPx
        ? "; device to CSS pixel ratio: " + r.devicePxPerCssPx
        : "");
}

Demo

Community
  • 1
  • 1
Starx
  • 72,283
  • 42
  • 174
  • 253
  • Does not work on Firefox for Android 4.4.2. Also in FF for mobile tick the `Always enable zoom` button in the Accessibility option. The demo won't react to the change. – lowtechsun Jan 25 '17 at 01:10
3

Although this is a 9 yr old question, the problem persists!

I have been detecting resize while excluding zoom in a project, so I edited my code to make it work to detect both resize and zoom exclusive from one another. It works most of the time, so if most is good enough for your project, then this should be helpful! It detects zooming 100% of the time in what I've tested so far. The only issue is that if the user gets crazy (ie. spastically resizing the window) or the window lags it may fire as a zoom instead of a window resize.

It works by detecting a change in window.outerWidth or window.outerHeight as window resizing while detecting a change in window.innerWidth or window.innerHeight independent from window resizing as a zoom.

//init object to store window properties
var windowSize = {
  w: window.outerWidth,
  h: window.outerHeight,
  iw: window.innerWidth,
  ih: window.innerHeight
};

window.addEventListener("resize", function() {
  //if window resizes
  if (window.outerWidth !== windowSize.w || window.outerHeight !== windowSize.h) {
    windowSize.w = window.outerWidth; // update object with current window properties
    windowSize.h = window.outerHeight;
    windowSize.iw = window.innerWidth;
    windowSize.ih = window.innerHeight;
    console.log("you're resizing"); //output
  }
  //if the window doesn't resize but the content inside does by + or - 5%
  else if (window.innerWidth + window.innerWidth * .05 < windowSize.iw ||
    window.innerWidth - window.innerWidth * .05 > windowSize.iw) {
    console.log("you're zooming")
    windowSize.iw = window.innerWidth;
  }
}, false);

Note: My solution is like KajMagnus's, but this has worked better for me.

dandeto
  • 567
  • 4
  • 12
  • 2
    I won't vote down because I can see you have a solution that works, and I understand what your code does, but I wouldn't recommend using a magic number such as `0.05` without at least providing a good explanation. ;-) For example, I know that in Chrome, specifically, 10% is the smallest amount that the browser will zoom in any case, but's not clear that this is true for other browsers. fyi, always make sure your code is tested, and be prepared to defend or improve it. – Nolo Oct 21 '18 at 14:24
  • interesting approach – oldboy May 18 '19 at 02:04
2

I'd like to suggest an improvement to previous solution with tracking changes to window width. Instead of keeping your own array of event listeners you can use existing javascript event system and trigger your own event upon width change, and bind event handlers to it.

$(window).bind('myZoomEvent', function() { ... });

function pollZoomFireEvent() 
{ 

    if ( ... width changed ... ) {
        $(window).trigger('myZoomEvent');
    }
}

Throttle/debounce can help with reducing the rate of calls of your handler.

Alexey
  • 644
  • 5
  • 13
  • 1
    Throttle/debounce is useful only if polling is triggered by some other events (e.g. mouse movement or keyup). – fuzzyTew Oct 15 '11 at 00:49
2

The resize event works on modern browsers by attaching the event on window, and then reading values of thebody, or other element with for example (.getBoundingClientRect()).

In some earlier browsers it was possible to register resize event handlers on any HTML element. It is still possible to set onresize attributes or use addEventListener() to set a handler on any element. However, resize events are only fired on the window object (i.e. returned by document.defaultView). Only handlers registered on the window object will receive resize events.

window.addEventListener("resize", getSizes, false)
        
function getSizes(){
  let body = document.body
  console.log(body.clientWidth +"px x "+ body.clientHeight + "px")
}

An other alternative: the ResizeObserver API

Depending your layout, you can watch for resizing on a particular element.

This works well on «responsive» layouts, because the container box get resized when zooming.

function watchBoxchange(e){
 // console.log(e[0].contentBoxSize.inlineSize+" "+e[0].contentBoxSize.blockSize)
 info.textContent = e[0].contentBoxSize.inlineSize+" * "+e[0].contentBoxSize.blockSize + "px"
}

new ResizeObserver(watchBoxchange).observe(fluid)
#fluid {
  width: 200px;
  height:100px;
  overflow: auto;
  resize: both;
  border: 3px black solid;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  font-size: 8vh
}
<div id="fluid">
   <info id="info"></info> 
</div>

Be careful to not overload javascript tasks from user gestures events. Use requestAnimationFrame whenever you needs redraws.

NVRM
  • 6,477
  • 1
  • 51
  • 60
1
<script>
var zoomv = function() {
  if(topRightqs.style.width=='200px){
  alert ("zoom");
  }
};
zoomv();
</script>
SchoolforDesign
  • 411
  • 5
  • 12
1

On iOS 10 it is possible to add an event listener to the touchmove event and to detect, if the page is zoomed with the current event.

var prevZoomFactorX;
var prevZoomFactorY;
element.addEventListener("touchmove", (ev) => {
  let zoomFactorX = document.documentElement.clientWidth / window.innerWidth;
  let zoomFactorY = document.documentElement.clientHeight / window.innerHeight;
  let pageHasZoom = !(zoomFactorX === 1 && zoomFactorY === 1);

  if(pageHasZoom) {
    // page is zoomed
    
    if(zoomFactorX !== prevZoomFactorX || zoomFactorY !== prevZoomFactorY) {
      // page is zoomed with this event
    }
  }
  prevZoomFactorX = zoomFactorX;
  prevZoomFactorY = zoomFactorY;
});
alwe
  • 1,444
  • 16
  • 19
1

Here is a clean solution:

// polyfill window.devicePixelRatio for IE
if(!window.devicePixelRatio){
  Object.defineProperty(window,'devicePixelRatio',{
    enumerable: true,
    configurable: true,
    get:function(){
      return screen.deviceXDPI/screen.logicalXDPI;
    }
  });
}
var oldValue=window.devicePixelRatio;
window.addEventListener('resize',function(e){
  var newValue=window.devicePixelRatio;
  if(newValue!==oldValue){
    // TODO polyfill CustomEvent for IE
    var event=new CustomEvent('devicepixelratiochange');
    event.oldValue=oldValue;
    event.newValue=newValue;
    oldValue=newValue;
    window.dispatchEvent(event);
  }
});

window.addEventListener('devicepixelratiochange',function(e){
  console.log('devicePixelRatio changed from '+e.oldValue+' to '+e.newValue);
});
fuweichin
  • 733
  • 7
  • 8
  • this seems like a pretty nice solution, and with a Proxy you wouldn't even need to clobber the widow property with your interceptor - do we know for sure that updating the DPR is what all browsers do/should do when the user has zoomed? er.. scratch that idea - window can't be proxied, maybe a solution exists using "matchMedia" but since it's just a true/false thing, i'm not sure how you'd test for changes to an analog value like the DPR... – Jon z Mar 11 '20 at 15:47
1

You can also get the text resize events, and the zoom factor by injecting a div containing at least a non-breakable space (possibly, hidden), and regularly checking its height. If the height changes, the text size has changed, (and you know how much - this also fires, incidentally, if the window gets zoomed in full-page mode, and you still will get the correct zoom factor, with the same height / height ratio).

Argo
  • 31
  • 1
0

According to MDN, "matchMedia" is the proper way to do this https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio#Monitoring_screen_resolution_or_zoom_level_changes

it's a bit finicky because each instance can only watch one MQ at a time, so if you're interested in any zoom level change you need to make a bunch of matchers.. but since the browser is in charge to emitting the events it's probably still more performant than polling, and you could throttle or debounce the callback or pin it to an animation frame or something - here's an implementation that seems pretty snappy, feel free to swap in _throttle or whatever if you're already depending on that.

Run the code snippet and zoom in and out in your browser, note the updated value in the markup - I only tested this in Firefox! lemme know if you see any issues.

const el = document.querySelector('#dppx')

if ('matchMedia' in window) {
  function observeZoom(cb, opts) {
    opts = {
      // first pass for defaults - range and granularity to capture all the zoom levels in desktop firefox
      ceiling: 3,
      floor: 0.3,
      granularity: 0.05,
      ...opts
    }
    const precision = `${opts.granularity}`.split('.')[1].length

    let val = opts.floor
    const vals = []
    while (val <= opts.ceiling) {
      vals.push(val)
      val = parseFloat((val + opts.granularity).toFixed(precision))
    }

    // construct a number of mediamatchers and assign CB to all of them
    const mqls = vals.map(v => matchMedia(`(min-resolution: ${v}dppx)`))

    // poor person's throttle
    const throttle = 3
    let last = performance.now()
    mqls.forEach(mql => mql.addListener(function() {
      console.debug(this, arguments)
      const now = performance.now()
      if (now - last > throttle) {
        cb()
        last = now
      }
    }))
  }

  observeZoom(function() {
    el.innerText = window.devicePixelRatio
  })
} else {
  el.innerText = 'unable to observe zoom level changes, matchMedia is not supported'
}
<div id='dppx'>--</div>
Jon z
  • 3,384
  • 1
  • 26
  • 20
-1

Here is a native way (major frameworks cannot zoom in Chrome, because they dont supports passive event behaviour)


//For Google Chrome
document.addEventListener("mousewheel", event => {
  console.log(`wheel`);
  if(event.ctrlKey == true)
  {
    event.preventDefault();
    if(event.deltaY > 0) {
      console.log('Down');
    }else {
      console.log('Up');
    }
  }
}, { passive: false });

// For Mozilla Firefox
document.addEventListener("DOMMouseScroll", event => {
  console.log(`wheel`);
  if(event.ctrlKey == true)
  {
    event.preventDefault();
    if(event.detail > 0) {
      console.log('Down');
    }else {
      console.log('Up');
    }
  }
}, { passive: false });

betontalpfa
  • 2,402
  • 1
  • 23
  • 44
-4

I'am replying to a 3 year old link but I guess here's a more acceptable answer,

Create .css file as,

@media screen and (max-width: 1000px) 
{
       // things you want to trigger when the screen is zoomed
}

EG:-

@media screen and (max-width: 1000px) 
{
    .classname
    {
          font-size:10px;
    }
}

The above code makes the size of the font '10px' when the screen is zoomed to approximately 125%. You can check for different zoom level by changing the value of '1000px'.

Saumil
  • 2,399
  • 4
  • 25
  • 53
  • What's the relationship between the `max-width` and the zoom ratio? – seebiscuit Jan 11 '16 at 22:56
  • This code will be executed on each <1000px screen and has nothing to do with zoom – Artur Stary Feb 17 '16 at 09:52
  • @ArturStary there is no way to detect if the browser is zoomed but there are many workarounds and one of them is what I have mentioned in my answer. Moreover, I did say that you can change the value of 1000px to check for different screen sizes which will give the effect of different zoom level – Saumil Feb 17 '16 at 20:58