Related
I'm building multi layered parallax effect on my site (html, css, js). Everything works quite well, but I've noticed that my parallax effect works really bad on Firefox, window.onscroll seems to be laggy, refresh rate so to speak is very low.
Here's my JS implementation:
document.addEventListener('DOMContentLoaded', function() {
const layers = document.querySelectorAll("[data-type='parallax']");
window.addEventListener('scroll', event => {
const topDistance = window.pageYOffset;
window.requestAnimationFrame(function() {
for (let i = 0; i < layers.length; ++i) {
const depth = layers[i].getAttribute('data-depth');
const movement = topDistance * depth;
const translate3d = 'translate3d(0, ' + movement + 'px, 0)';
layers[i].style.transform = translate3d;
}
})
});
});
My html code:
<div class="parallax-banner">
<div class="parallax-layer layer-1" data-type="parallax" data-depth="0.05"></div>
<div class="parallax-layer layer-2" data-type="parallax" data-depth="0.2"></div>
<div class="parallax-layer layer-3" data-type="parallax" data-depth="0.4"></div>
<div class="parallax-layer layer-4" data-type="parallax" data-depth="0.6"></div>
<div class="parallax-layer layer-5" data-type="parallax" data-depth="0.7"></div>
<div class="parallax-layer layer-6" data-type="parallax" data-depth="0"></div>
</div>
Have you encountered it? Is it typical issue? How can I fix that?
I have the following very simple JS-implementation with two layers in the back, which was unusable with Firefox due to jittering and laggy behaviour:
$(function() {
$(window).on('scroll', function() {
$('#background').css('background-position-y', $(window).scrollTop() * -.15);
});
});
$(function() {
$(window).on('scroll', function() {
$('#background2').css('background-position-y', $(window).scrollTop() * -.09);
});
});
CSS-only alternatives didnt work for me as it caused the background layers to visibly overflow after my contents end.
Finally I found a way to improve the performance desktop Firefox (not on mobile yet). I added
position: fixed;
background-attachment: fixed;
background-position: top;
to all my background layers.
Still no improvement in iOS Safari and mobile Firefox.
There are several bug reports for Firefox since version 16.
On my long way searching the internet for solutions i also found and added a script by keithclark but I'm not sure if this makes any difference at all, the script is from 2011:
/*
Firefox super responsive scroll (c) Keith Clark - MIT Licensed
*/
(function(doc) {
console.log("Document executed")
var root = doc.documentElement,
scrollbarWidth, scrollEvent;
// Not ideal, but better than UA sniffing.
if ("MozAppearance" in root.style) {
// determine the vertical scrollbar width
scrollbarWidth = root.clientWidth;
root.style.overflow = "scroll";
scrollbarWidth -= root.clientWidth;
root.style.overflow = "";
// create a synthetic scroll event
scrollEvent = doc.createEvent("UIEvent")
scrollEvent.initEvent("scroll", true, true);
// event dispatcher
function scrollHandler() {
doc.dispatchEvent(scrollEvent)
}
// detect mouse events in the document scrollbar track
doc.addEventListener("mousedown", function(e) {
if (e.clientX > root.clientWidth - scrollbarWidth) {
doc.addEventListener("mousemove", scrollHandler, false);
doc.addEventListener("mouseup", function() {
doc.removeEventListener("mouseup", arguments.callee, false);
doc.removeEventListener("mousemove", scrollHandler, false);
}, false)
}
}, false)
// override mouse wheel behaviour.
doc.addEventListener("DOMMouseScroll", function(e) {
// Don't disable hot key behaviours
if (!e.ctrlKey && !e.shiftKey) {
root.scrollTop += e.detail * 16;
scrollHandler.call(this, e);
e.preventDefault()
}
}, false)
}
})(document);
You can test it by pasting it to the console.
I hope I could at least help a little bit.
I'm writing a simple drawing "thing" for our users. It uses SVG. All of the users have iPads, either 4s or Air 2s. I'm testing on a 2 (2nd gen, not Air 2). All iPads are running iOS 9.2 and using Safari.
When free-hand drawing on the SVG element, there is substantial lag. On the 2nd gen it's "almost tolerable", on the 4s it's "incredibly painful". Not sure about the Air 2s, but if it's scaling the way the 4s did, I'd say it's "kill me now". Interesting how the better hardware lags more, but I suppose it could have something to do with the increased resolution, but still...
Is there anything I can do to improve the performance? The lag is also noticible when drawing ready shapes (rectangle, line (path), and ellipse), but it's something we can live with.
Here's the code I'm using to bind the event handlers (using jQuery) for the free-hand drawing.
Floor.bindFreeHand = function (e) {
e.preventDefault();
var sPB, d, p1, segments;
Floor._jElement.off(EventTypes.pointerDown + " " + EventTypes.pointerUp).on(EventTypes.pointerDown, function (e) {
var pI1 = PointerInfo.parse(e);
sPB = new SvgPathsBuilder();
d = sPB.moveTo(pI1.x, pI1.y).d();
p1 = Svg.path(d).attr({
"fill": "none",
"stroke": Floor._color,
"stroke-linecap": "round",
"stroke-linejoin": "round",
"stroke-opacity": Floor._opacity,
"stroke-width": Floor._width
});
segments = p1.element.pathSegList;
Floor._sElement.add(p1);
Floor._jElement.on(EventTypes.pointerMove, function (e) {
e.preventDefault();
e.stopPropagation();
segments.appendItem(p1.element.createSVGPathSegLinetoAbs(e.offsetX, e.offsetY));
//var pI2 = PointerInfo.parse(e);
//d = sPB.lineTo(pI2.x, pI2.y).d();
//p1.attr("d", d);
});
}).on(EventTypes.pointerUp, function (e) {
Floor._jElement.off(EventTypes.pointerMove);
});
};
And here's the PointerInfo and EventTypes objects:
var PointerInfo = (function () {
function PointerInfo(x, y) {
this.x = x;
this.y = y;
}
PointerInfo.parse = function (e) {
var x = e.offsetX - 2, y = e.offsetY - 1;
return new PointerInfo(x, y);
};
return PointerInfo;
})();
var EventTypes = (function () {
function EventTypes() {
}
EventTypes.touch = EventTypes.touch || ("ontouchstart" in window);
EventTypes.pointerDown = EventTypes.touch ? "touchstart" : "mousedown";
EventTypes.pointerMove = EventTypes.touch ? "touchmove" : "mousemove";
EventTypes.pointerUp = EventTypes.touch ? "touchend" : "mouseup";
EventTypes.pointerLeave = EventTypes.touch ? "touchleave" : "mouseout";
return EventTypes;
})();
All of these are compiled down from TypeScript.
I figured it out, after spending two days on it and disabling everything else on the page and every other script being loaded. It turned out that it was CSS that was causing the lag. The SVG element that was being drawn on had a background pattern created via gradients to look like a grid. Apparently as path was being updated on touchmove the element's layout was invalidated and Safari was force redrawing it constantly.
So, the moral of the story is that the performance issues in iOS Safari aren't always due to JavaScript. Fancy CSS effects also contribute to it especially if the element they're on is being manipulated constantly.
UPDATE
With Chrome 48, you can no longer set the path's segments because that API was deprecated and removed. The solution now is to constantly update the d attribute as the pointer moves. From my testing, there's no noticeable performance penalty, even on older devices such as the iPad 2nd gen.
I am trying to create a sort of plugin or event that css transitions (moves) elements via swipe on ipad. For this I am using so far the brillant working little code snippet of cocco:
(function(D){
var M=Math,abs=M.abs,max=M.max,
ce,m,th=20,t,sx,sy,ex,ey,cx,cy,dx,dy,l,
f={
touchstart:function(e){
t=e.touches[0];
sx=t.pageX;
sy=t.pageY
},
touchmove:function(e){
m=1;
t=e.touches[0];
ex=t.pageX;
ey=t.pageY
},
touchend:function(e){
ce=D.createEvent("CustomEvent");
ce.initCustomEvent(m?(
max(dx=abs(cx=ex-sx),dy=abs(cy=ey-sy))>th?
dx>dy?cx<0?'swl':'swr':cy<0?'swu':'swd':'fc'
):'fc',true,true,e.target);
e.target.dispatchEvent(ce);
m=0
},
touchcancel:function(e){
m=0
}
}
for(l in f)D.addEventListener(l,f[l],false)
})(document);
For the transitions rico st.cruz’ plugin jquery-transit.js is implemented on my site (and jquery of course)
// usage (example)
$("body").on( 'swu', function() {
fullscreenSwipeUp = true;
$("#Fullscreen").transition( { y: '-100%' }, 900, 'easeInSine' )
} );
So far so good.
Now my idea was to indicate the possible „swipe up“ with an additional touch move event that moves the element along while the finger tap is going on. And I succeeded in doing so adding the following js:
// js parts to integrate in a cooler way
var startY;
window.addEventListener( 'touchstart', function(e) {
e.preventDefault();
startY = e.targetTouches[0].pageY;
}, false );
window.addEventListener( 'touchmove', function(e) {
e.preventDefault();
// check if transition is going on and animations are ready
if ( !fullscreenSwipeUp ) { // find better solution f.e. to dispatch event on fullscreen transition?
var diffY = e.changedTouches[0].pageY - startY;
// fullscreen transition
if ( diffY <= 0 ) {
$("#Fullscreen").css( { y: diffY } );
} else {
// snap to clean starting value at bottom
$("#Fullscreen").css( { y: 0 } );
};
// do something else to indicate that swipe will be concluded when finger leaves
// min value based on variable th from custom swipe event
if ( diffY < -20 ) {
// indicate that swipe will be concluded
} else {
// indicate that swipe will not conclude
};
};
}, false );
// fullscreen fall back to bottom if swipe not concluded
window.addEventListener( 'touchend', function(e) {
e.preventDefault();
if ( !fullscreenSwipeUp ) {
// fall back to starting values / dequeue for smoother transition on second time
$("#Fullscreen").dequeue().transition( { y: 0 }, 150, 'easeInSine' );
};
}, false );
Now I see also that in this code various parts overlap each other as the double touch move event from the base custom event and my stuff. It would be so awesome if someone could offer me an idea of how to integrate the two codes. Or maybe something likes this exists? I couldn’t find the right thing.
Thanks for a helping hand!
PS I tried to create a fiddle, also, here: http://jsfiddle.net/Garavani/9zosg3bx/1/
but sadly I am too stupid to make it work with all the external scripts :-(
jquery-transit.js:
http://ricostacruz.com/jquery.transit/
EDIT:
So I changed my code in the following way.
I guess I was confused between the original swipe event (which incorporates the touch move event also) and what I wanted to do with the element.
It works although probably still a mess.
I had to set kind of flag to check if the swipe is executed to disable the touch move event temporarily. Is there a better way to do that? Thanks for your patience!
var startY;
var swipeY = false; // for condition if condition for swipe is fullfilled
var swiped = false; // for check if swipe has been executed
window.addEventListener( 'touchstart', function(e) {
e.preventDefault();
startY = e.targetTouches[0].pageY;
}, false );
window.addEventListener( 'touchmove', function(e) {
e.preventDefault();
// check if swipe already executed
if ( !swiped ) { // find better solution f.e. to dispatch event when swiped?
var diffY = e.changedTouches[0].pageY - startY;
// check for final swipe condition
if ( diffY < -30 ) {
swipeY = true;
// do something with an element to indicate swipe will be executed
} else {
swipeY = false;
// do something with an element to indicate swipe will NOT be executed
};
};
}, false );
window.addEventListener( 'touchend', function(e) {
e.preventDefault();
if ( swipeY ) {
swiped = true;
// trigger the swipe action
} else {
// let the element fall back to original state
};
}, false );
EDIT 2:
var startY;
var swipeY = false;
var swiped = false;
var wh = $(window).height();
// fullscreen move on touchmove
function fm(e) {
e.preventDefault();
// check if transition is going on and animations are ready
if ( !swiped && ready ) { // !swiped is necessary, also / why?
var diffY = e.changedTouches[0].pageY - startY;
var scaleY = diffY/wh;
// calculate transition intermediate values
var osh = 0.91 - ( 0.09 * scaleY );
var ofsc = 0.86 - ( 0.14 * scaleY );
// fullscreen transition
if ( diffY <= 0 ) {
tfs(osh,[1,-scaleY],ofsc,diffY,0) // an extra module that does the animation with the calculated values
} else {
// snap to clean starting values at bottom
tfs(0.91,[1,0],0.86,0,0)
};
// rotate arrow to indicate surpassing minimum touch move leading to swipe
if ( diffY < -30 ) {
swipeY = true;
$arrowDown.addClass('rotation');
} else {
swipeY = false;
$arrowDown.removeClass('rotation');
};
};
};
// fullscreen swipe
function fs(e) {
e.preventDefault();
window.removeEventListener( 'touchmove', fm, false );
if ( !swiped ) { // this is necessary, also / why?
if ( swipeY ) {
$arrowbottomField.trigger('touchstart'); // trigger the full swipe (also available as element direct touch (on touch swiped is set to true in touchstart event handler, also)
window.removeEventListener( 'touchend', fs, false );
} else {
// fall back of animation to starting values
tfs(0.91,[1,0],0.86,0,0);
};
};
};
window.addEventListener( 'touchstart', function(e) {
e.preventDefault();
startY = e.targetTouches[0].pageY;
window.addEventListener( 'touchmove', fm, false );
}, false );
window.addEventListener( 'touchend', fs, false );
Explanation (as good as I can)
Thanks Cocco for your patience again.
In the meantime I modified the code again.
It works :-) I know it will be a mess in your eyes.
On my site (supposed to work also on ipad, but not only) there is a field where you can touch and a fullscreen is moving up covering the whole window (similar to www.chanel.com -> „more“).
Additionally it is (on ipad) possible to swipe on the whole body to do the same thing.
Phases:
Phase 1) User taps and swipes (keeping the finger on) The fullscreen div follows his finger.
Pause 2) If a certain distance of the swipe is reached (still with the finger on) a little arrow also turns to indicate: if you leave now, swipe will be executed
Pause 3) still with finger on the distance becomes less than the one to complete the swipe, the arrow turns back
Phase 4) Leave with the finger the distance already covered decides if the fullscreen will move up completely or fall back down (if distance not sufficient)
For the little arrow to turn I could use a class toggle as you told me (way better than what I did! Thanks) but for the rest of the animation not because the values strongly depend on the window size. And the idea of my whole site is that the browser window can have any sizes or relations, all contents adapt themselves (containing horizontal AND vertical centerings at any time)
For mobile phones everything changes completely.
If you want to take a look: www.stefanseifert.com (if you want to see the effect (on ipad) you will have to skip the intro by touching the arrow in the right corner on the bottom of the trailer div)
I know that there is tons of other stuff that is note state of the art programming (to say this very carefully :-) but in a way it works.
And I can not afford to hire a programmer for weeks to redo everything. ;-)
It’s kind of learning by doing (for as you can guess I never programmed before in my life ;-)
so I am very thankful for every bit of information and help to do little things better.
If someone will pass here I will get some down votes for sure :-)
Thanks Cocco
Merry Christmas!
The code is actually made for low cpu devices , high performance .. leaving out complex calculations inside the touchmove. Anyway to animate your elements (between touchstart & touchend) a simple solution is to use css (not jquery or other resourceintensive plugins).
then just think logically... you can't determine in wich direction you swipe at the touchstart... you can do that at the touchend event after doing some math. OR while touchmove wich destroys the whole code, as mentioned above.
OPTION 1 (simple & high performance)
DEMO
http://jsfiddle.net/z7s8k9r4/
Add some lines....
(function(D){
var M=Math,abs=M.abs,max=M.max,
ce,m,th=20,t,sx,sy,ex,ey,cx,cy,dx,dy,l,
T, //<- THE TARGET
f={
touchstart:function(e){
t=e.touches[0];
sx=t.pageX;
sy=t.pageY;
T=e.target; T.classList.add('sw'); //<- ADD A CUSTOM CLASS FOR SWIPES
},
touchmove:function(e){
m=1;
t=e.touches[0];
ex=t.pageX;
ey=t.pageY
},
touchend:function(e){
ce=D.createEvent("CustomEvent");
ce.initCustomEvent(m?(
max(dx=abs(cx=ex-sx),dy=abs(cy=ey-sy))>th?
dx>dy?cx<0?'swl':'swr':cy<0?'swu':'swd':'fc'
):'fc',true,true,e.target);
e.target.dispatchEvent(ce);
m=0;
T.classList.remove('sw'); //<- REMOVE THE CLASS
},
touchcancel:function(e){
m=0
}
}
for(l in f)D.addEventListener(l,f[l],false)
})(document);
now define the css class
element.sw{
background-color:yellow;
}
use the proper (HW) animation for mobile devices on your element
element{
background-color:green;
-webkit-transition:background-color 200ms ease;
}
this is a simple and dirty solution , it works. just keep it simple.
the class will apply only on defined elements. ;)
OPTION 2
forget the code above...
Here is another "performant" way to do a "snap" animation using "bounce".
the code is a little more complex... and uses the mouse.. replace the mouse events with touch events.
http://jsfiddle.net/xgkbjwxb/
and with more data points
http://jsfiddle.net/xgkbjwxb/1/
note: don't use jquery or complex js plugins in mobile devices.
Extra
As it's really problematic to work on pc without a touch interface i added some lines that force the mouse to simulate touches based on my function requisites.
http://jsfiddle.net/agmyjwb0/
EDIT
this code should do what you want...
http://jsfiddle.net/xgkbjwxb/3/
I currently have html enabled tooltips that also display "sub graphs". However, it would be nice if it was possible to have all tooltips pop up in a fixed location or have an offset that adjusted their relative poition.
This is an example of the kind of tooltip that I have (blank data). I'd like to move it to the right. Any suggestions would be appreciated, including any javascript trickery.
whilst the answer is very good it is a little outdated now. Google has implemented CSS control so there is greater flexibility without the need to hack the JavaScript.
.google-visualization-tooltip { position:relative !important; top:0 !important;right:0 !important; z-index:+1;}
will provide a tooltip fixed at the bottom of the chart, live example: http://www.taxformcalculator.com/federal-budget/130000.html
alternatively you could just tweak the left margin...
.google-visualization-tooltip { margin-left: 150px !important; z-index:+1;}
Note that pulling the container forward with z-index reduces (but does not stop entirely) visibility flicker as the mouse moves. The degree of flicker will vary on chart size, call etc. Personally, I prefer to fix the tool tip and make it part of the design as per the first example. Hope this helps those who are deterred by the JS hack (which is good but really no longer necessary).
The tooltip position is set inline, so you need to listen for DOM insertion of the tooltip and change the position manually. Mutation events are deprecated, so use a MutationObserver if it is available (Chrome, Firefox, IE11) and a DOMNodeInserted event handler if not (IE 9, 10). This will not work in IE8.
google.visualization.events.addOneTimeListener(myChart, 'ready', function () {
var container = document.querySelector('#myChartDiv > div:last-child');
function setPosition () {
var tooltip = container.querySelector('div.google-visualization-tooltip');
tooltip.style.top = 0;
tooltip.style.left = 0;
}
if (typeof MutationObserver === 'function') {
var observer = new MutationObserver(function (m) {
for (var i = 0; i < m.length; i++) {
if (m[i].addedNodes.length) {
setPosition();
break; // once we find the added node, we shouldn't need to look any further
}
}
});
observer.observe(container, {
childList: true
});
}
else if (document.addEventListener) {
container.addEventListener('DOMNodeInserted', setPosition);
}
else {
container.attachEvent('onDOMNodeInserted', setPosition);
}
});
The MutationObserver should be fine, but the events may need some work; I didn't test them.
I had more or less the same question as Redshift, having been trying to move the tooltip relative to the node being hovered over. Using asgallant's fantastic answer I've implemented his code as below.
I haven't been able to test whether this works with the MutationObserver because during my testing in Firefox, Chrome and IE11 it always fails that test and uses addEventListener. The docs suggest it should work though.
I had to introduce a timeout to actually manipulate the styles as otherwise the left and top position of the element was always reported as 0. My assumption is that the event fired upon addition of the node but the DOM wasn't quite ready. This is just a guess though and I'm not 100% happy with implementing it in this way.
var chart = new google.visualization.LineChart(document.getElementById('line_chart'));
google.visualization.events.addOneTimeListener(chart, 'ready', function () {
var container = document.querySelector('#line_chart > div:last-child');
function setPosition(e) {
if (e && e.target) {
var tooltip = $(e.target);
setTimeout(function () {
var left = parseFloat(tooltip.css('left')) - 49;
var top = parseFloat(tooltip.css('top')) - 40;
tooltip.css('left', left + 'px');
tooltip.css('top', top + 'px');
$(".google-visualization-tooltip").fadeIn(200);
}, 1);
}
else {
var tooltip = container.querySelector('.google-visualization-tooltip');
var left = parseFloat(tooltip.style.left) - 49;
var top = parseFloat(tooltip.style.top) - 40;
tooltip.style.left = left + 'px';
tooltip.style.top = top + 'px';
$(".google-visualization-tooltip").fadeIn(200);
}
}
if (typeof MutationObserver === 'function') {
var observer = new MutationObserver(function (m) {
if (m.length && m[0].addedNodes.length) {
setPosition(m);
}
});
observer.observe(container, {
childList: true
});
}
else if (document.addEventListener) {
container.addEventListener('DOMNodeInserted', setPosition);
}
else {
container.attachEvent('onDOMNodeInserted', setPosition);
}
});
chart.draw(data, options);
}
EDIT: Updated to get the MutationObserver working following asgallant's comment.
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.
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.
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
Good news everyone some people! Newer browsers will trigger a window resize event when the zoom is changed.
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);
})();
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.
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
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.
⬤ 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.
⚠️ Do resize your tab, or zoom, to trigger this snippet:
window.addEventListener("resize", getSizes, false)
function getSizes(){
let body = document.body
body.width = window.innerWidth
body.height = window.innerHeight
console.log(body.width +"px x "+ body.height + "px")
}
getSizes()
⬤ An other modern 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){
info.textContent = e[0].contentBoxSize[0].inlineSize+" x "+e[0].contentBoxSize[0].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.
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.
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>
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).
<script>
var zoomv = function() {
if(topRightqs.style.width=='200px){
alert ("zoom");
}
};
zoomv();
</script>
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;
});
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);
});
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 });
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'.