Always listen to window screen changes - javascript

How do I make the code always respond to the changes in screen size?
I remember there is one globalEventHandler can do this but I'm not sure which one...
For example, the div#test-02 will always automatically adjust its width to 1/3 of the div#test-01. The problem is that as we use the developer tool (f12) to resize the window, the width of div#test-01 is keep changing but the code won't respond to it anymore...Unless we reload the page...
const test01 = document.querySelector('#test-01');
const test02 = document.querySelector('#test-02');
const data = test01.getBoundingClientRect().width;
test02.style.width = `${data / 3}px`;
#test-01 {
width: 100vw;
height: 50vh;
background-color: grey;
display: flex;
justify-content: center;
align-items: center;
}
#test-02 {
height: 100%;
background-color: orange;
}
<div id="test-01">
<div id="test-02"></div>
</div>

To listen for window screen changes, you can use
window.addEventListener("resize", callback)
This will listen for resize events on the window, and execute the callback function if such an event occurs. So you could put the code which resizes your elements relative to each other into the callback, so that it is executed on every window resize. (The callback will not be run on page load, so you would have to run your code outside of the event handler once too.)
The following code should work in your case:
const test01 = document.querySelector('#test-01');
const test02 = document.querySelector('#test-02');
function onResize() {
const data = test01.getBoundingClientRect().width;
test02.style.width = `${data / 3}px`;
}
window.addEventListener("resize", onResize, {passive: true})
onResize()

Related

Why is event listener "resize" slower than css #media screen?

I'm showing and hiding different headings depending on the device screen width. As react allows me to mount and unmount a component depending on a state I sometimes used the event listener addEventLister("resize", handleResize) to show and hide elements.
But with this method on a new page refresh, some flickering appeared as the default value of state was replaced by the actual evaluated value of the screen width, which caused the bigger typo to be shown for a millisecond before it was hidden again.
I discovered this won't happen with #media screen and display: none.
Why is it so slow? And is there any workaround for cases where I can't solve it in CSS, so a way to prioritize the event listener to evaluate before showing the wrong heading?
What's the go-to way for these scenarios, as this must be a basic issue on all responsive sites?
Example for a custom hook to listen for window changes:
const [screenSize, setScreenSize] = useState<Size>({
width: 0,
height: 0,
});
useEffect(() => {
const handleResize = () => {
setScreenSize({ width: window.innerWidth, height: window.innerHeight });
};
addEventListener("resize", handleResize);
handleResize();
return () => removeEventListener("resize", handleResize);
}, []);
Css example to do the same:
.title {
display: flex;
}
#media screen and (max-width: 768px) {
.title {
display: none;
}
}

Trigger IntersectionObserver when element is already in viewport

The IntersectionObserver is triggered when an element is visible in the viewport for a certain amount (0-100%). This means, when the element is already 100% in the viewport it does not trigger anymore, as there is no change on the threshold.
I have a element that has a height of 200vh and I want the IntersectionObserver to trigger, when I scroll over this element. So the element is always 100% inside the viewport.
Is there a way to trigger the observer while scrolling over the element?
I cannot use the scroll event, as I am using a CSS scroll-snap, which causes the event to be swallowed by the browser, before JS can detect it.
Hopefully I was able to grasp your challenge here, so I'll attempt to propose a solution that should work for your use case, even though there's no code to use as a reference.
From my understanding you're using scroll-snap to snap sections as the user interacts by doing scroll and your intention is to have the Intersection Observer to trigger as the users move from section to section.
In the following example you'll see how sections are being snapped but the debugger shows which section is being shown to the user.
const debuggerSpan = document.querySelector('#current-section');
const sections = [...document.querySelectorAll('.scroll-snap-item')];
const div = document.querySelector('.scroll-snap-container');
/*
* This method will get called any time a section touches the top
* of the viewport.
*/
const intersectionDetected = (entries, observer) => {
entries.forEach((entry) => {
const {
innerText
} = entry.target;
if (!entry.isIntersecting) return;
// Making it obvious that the current section is correct.
debuggerSpan.innerText = innerText;
});
};
const observer = new IntersectionObserver(intersectionDetected, {
/*
* Root should be div and not the default (doc).
*/
root: div,
/*
* Negative margin from the bottom creates an invisible line
* to detect intersections.
*
* The reason why the recommendation is to use -1% and -99% is to
* avoid the race condition between two intersections happening
* (AKA the section about to be out of the viewport and the section
* about to enter the viewport).
*/
rootMargin: '-1% 0% -99% 0%',
/*
* Default value but making it explicit as this is the
* only configuration that works.
*/
threshold: 0
});
sections.forEach(section => observer.observe(section));
.scroll-snap-item {
height: 100vh;
display: grid;
place-items: center;
font-size: 4rem;
scroll-snap-align: start;
}
.scroll-snap-container {
scroll-snap-type: y mandatory;
overflow-y: scroll;
height: 100vh;
}
/* Decorative stuff below*/
.scroll-snap-item:nth-child(odd) {
background-color: gray;
}
aside {
position: fixed;
font-size: 1.6rem;
bottom: 16px;
right: 16px;
background-color: #333;
color: white;
padding: 16px;
border-radius: 32px;
}
<div class="scroll-snap-container">
<section class="scroll-snap-item">1</section>
<section class="scroll-snap-item">2</section>
<section class="scroll-snap-item">3</section>
<section class="scroll-snap-item">4</section>
<section class="scroll-snap-item">5</section>
<section class="scroll-snap-item">6</section>
</div>
<aside>Current section: <span id="current-section"></span></aside>
I wrote a couple of practical posts that cover what's behind all this and what was the thought process to address this situation. Please feel free to give it a read and leave a comment if things are not clear enough:
Scrollspying made easy with the Intersection Observer API.
A graphical introduction to the Intersection Observer API.
Both are quick reads and should provide everything you need to tackle this and even more complex problems with the Intersection Observer. Also, feel free to play around with this tool I wrote called The Intersection Observer Playground where you can try out different configurations and see how they affect the intersection triggers.
Hope this is helpful!

JavaScript window resize event is slow. How to optimize window resize event

When I click on the Maximize button of the browser window, a function I wrote that will execute when the window is resized, does not work properly I think because JavaScript window resize event is running slow. It worked when I used the mouse to resize the window. Also, when I tried to change between portrait mobile to landscape mobile it is also slow.
Faced a problem with using with window.addEventListener('resize', aFunction), page gets slow. These events are generated multiple times per second, and if the event handler takes too much time, the browser won’t catch with redrawing the page.
function aFuntion() {
let div1 = document.querySelector('.div1');
let div2 = document.querySelector('.div2');
let diff = div1.clientHeight - div2.clientHeight;
div2.style.top = diff + 'px';
};
// Call the function.
// This worked!
aFuntion();
// Call the function when users resize the window.
// This worked!
window.addEventListener('resize', aFuntion);
// Users click on the Maximize button of the window.
// The function does not work properly!
// Portrait mobile to Landscape mobile.
// The function does not work properly!
.div1 {
position: relative;
}
.div2 {
position: absolute;
width: 300px;
top: 0;
transition: all 0.5s ease;
}
#media only screen and (max-width: 1024px) {
.div2 {
width: 200px;
}
}
<div class="div1">
<div class="div2"></div>
</div>
I removed transition: all 0.5s ease; and it worked for a while now it is not working again.
So, I know now it is because of window.addEventListener('resize', function) is running slow.
Try this:
var timeout = false;
window.addEventListener('resize', function() {
clearTimeout(timeout);
timeout = setTimeout(aFuntion, 200);
});
Read: https://bencentra.com/code/2015/02/27/optimizing-window-resize.html
try with document.addEventListener('resize', aFuntion);

Natural window resize event invalidating layout in Chrome, forced window resize does not.

Demonstration: http://jsfiddle.net/calvintennant/NrJ8T/show/
When I force a window resize by doing: $(window).resize() my listener is called, and everything is fine. However if I actually resize the window, I'm getting multiple resize events called within the same frame.
Timeline during forced resize:
Timeline during natural resize:
Is this a bug in Chrome, or am I misunderstanding something?
As pointed out by #avram-lavinsky, resize events can be called multiple time per frame.
Updated example using Request Animation Frame (seen first here https://developer.mozilla.org/en-US/docs/Web/Reference/Events/resize):
http://jsfiddle.net/calvintennant/v69WW/
# HTML
<div class="box-1"></div>
<div class="box-2"></div>
<div class="box-3"></div>
# CSS
html, body {
margin: 0;
}
.box-1 {
background: #00F;
bottom: 0;
position: absolute;
width: 100%;
}
.box-2 {
background: #0F0;
height: 30px;
position: relative;
}
.box-3 {
background: #F0F;
height: 66px;
position: relative;
}
# JS
var box1 = $('.box-1');
var box2 = $('.box-2');
var box3 = $('.box-3');
var drawing = false;
var resizeFired = false;
var requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame;
$(window).resize(function() {
// set resizedFired to true and execute drawResize if it's not already running
if (drawing === false) {
resizeFired = true;
drawResize();
}
});
function drawResize() {
var height;
// render friendly resize loop
if (resizeFired === true) {
resizeFired = false;
drawing = true;
height = $(window).height();
height -= $(box2).height();
height -= $(box3).height();
$(box1).height(height);
requestAnimationFrame(drawResize);
} else {
drawing = false;
}
}
$(window).resize();
Window resize as a user action is real-time event. It fires many times as the user drags.

JavaScript event for canvas resize

It appears that altering the dimensions of a canvas clears any drawing already done on that canvas.
Is there an event that fires on canvas resize, so I can hook it and redraw when it occurs?
Update 2020
Jemenake's answer looks good, but in general I would recommend loading in a library which provides debounce functionality, as that's what this is called.
For example with the lodash one, you can do window.addEventListner('resize', _.debounce(onResize, 500). Note that there is a third argument where you can specify the behavior you want (e.g. window.addEventListner('resize', _.debounce(onResize, 500, {leading: true, trailing true})), although the default should be pretty good.
Kit Sunde's answer will do a lot of unnecessary work whilst the browser window is not resized. It is better to check whether the event got resized in response to a browser event and next ignore resize events for a given amount of time after which you do a check again (this will cause two checks after eachother in quick succession and the code could be improved to prevent this, but you get the idea probably).
(function(){
var doCheck = true;
var check = function(){
//do the check here and call some external event function or something.
};
window.addEventListener("resize",function(){
if(doCheck){
check();
doCheck = false;
setTimeout(function(){
doCheck = true;
check();
},500)
}
});
})();
Please note, the code above was typed blindly and not checked.
David Mulder's answer is an improvement, but it looks like it will trigger after waiting timeout milliseconds after the first resize event. In other words, if more resizes happen before the timeout, it doesn't reset the timer. I was looking for the opposite; I wanted something which would wait a little bit of time to let the resize events stop, and then fire after a certain amount of time after the last one. The following code does that.
The ID of any currently-running timer will be in timer_id. So, whenever there's a resize, it checks to see if there's already a time running. If so, it cancels that one and starts a new one.
function setResizeHandler(callback, timeout) {
var timer_id = undefined;
window.addEventListener("resize", function() {
if(timer_id != undefined) {
clearTimeout(timer_id);
timer_id = undefined;
}
timer_id = setTimeout(function() {
timer_id = undefined;
callback();
}, timeout);
});
}
function callback() {
// Here's where you fire-after-resize code goes.
alert("Got called!");
}
setResizeHandler(callback, 1500);
You usually don't want to strictly check for a resize event because they fire a lot when you do a dynamic resize, like $(window).resize in jQuery and as far I'm aware there is no native resize event on elements (there is on window). I would check it on an interval instead:
function onResize( element, callback ){
var elementHeight = element.height,
elementWidth = element.width;
setInterval(function(){
if( element.height !== elementHeight || element.width !== elementWidth ){
elementHeight = element.height;
elementWidth = element.width;
callback();
}
}, 300);
}
var element = document.getElementsByTagName("canvas")[0];
onResize( element, function(){ alert("Woo!"); } );
I didn't find any build-in events to detect new canvas dimensions, so I tried to find a workaround for two scenarios.
function onresize()
{
// handle canvas resize event here
}
var _savedWidth = canvas.clientWidth;
var _savedHeight = canvas.clientHeight;
function isResized()
{
if(_savedWidth != canvas.clientWidth || _savedHeight != canvas.clientHeight)
{
_savedWidth = canvas.clientWidth;
_savedHeight = canvas.clientHeight;
onresize();
}
}
window.addEventListener("resize", isResized);
(new MutationObserver(function(mutations)
{
if(mutations.length > 0) { isResized(); }
})).observe(canvas, { attributes : true, attributeFilter : ['style'] });
For detecting any JavaScript canvas.style changes, the MutationObserver API is good for. It doesn't detect a specific style property, but
in this case it is sufficient to check if canvas.clientWidth and canvas.clientHeight has changed.
If the canvas size is declared in a style-tag with a percent unit, we have a problem to interpret the css selectors correctly (>, *, ::before, ...) by using JavaScript. I think in this case the best way is to set a window.resize handler and also check the canvas client size.
Have you tried ResizeObserver (caniuse.com) in addition to the debounce logic mentioned in the other answers like Jemenake's?
For example:
const pleasantText = 'There is a handle! Let\'s try relaxing while resizing the awesome canvas! ->';
onResize = (entries) => {
document.getElementsByTagName('span')[0].innerHTML =
`${pleasantText} <b>${entries[0].target.offsetWidth}x${entries[0].target.offsetHeight}</b>`;
};
const canvas = document.getElementsByTagName('canvas')[0];
const observer = new ResizeObserver(onResize);
observer.observe(canvas);
body > div:first-of-type {
display: grid;
place-items: center;
width: 100vw;
height: 100vh;
user-select: none;
}
body > div:first-of-type div {
display: grid;
min-width: 12em;
min-height: 6em;
width: 16rem;
height: 6rem;
background: #ccc;
overflow: auto;
place-items: center;
text-align: center;
resize: both;
grid-auto-columns: 20vw 1fr;
grid-auto-flow: column;
grid-gap: 20px;
padding: 0.4rem 0.8rem;
border-radius: 4px;
}
canvas {
width: 100%;
height: 100%;
min-width: 0;
min-height: 0;
background: #ff9;
}
<div>
<div>
<span></span>
<canvas></canvas>
</div>
</div>
save the canvas state as imageData and then redraw it after resizing
var imgDat=ctx.getImageData(0,0,ctx.canvas.width,ctx.canvas.height)
after resize
ctx.putImageData(imgDat,0,0)

Categories