Removing an element on resize doesn't work (but adding does)

To learn how to control display breakpoints in JavaScript, I am trying to add an element after another with the added element only appearing in desktop displays and disappearing in mobile displays.

I managed to cause the element to be added after Sitepoint’s logo in sitepoint.com (initially and in resize), but my problem is that it isn’t removed when the window is resized.

What may cause that problem and how would you solve it?

function addPhoneNumberBox() {
    document.querySelector(".header-module--logo--B7flj").insertAdjacentHTML('afterend', `
        <div id="phoneNumberBox" dir="ltr" style="text-align: center;">
            <h2>Call me:</h2>
            <a tel:X>+1 012-3456780</a>
        </div>
    `);
};

const addedPhoneNumberBox = document.querySelector("#phoneNumberBox");

if (window.innerWidth >= 850) {
    addPhoneNumberBox();
}

window.addEventListener("resize", function() {
    if (window.innerWidth < 850) {
        addedPhoneNumberBox.parentElement.removeChild(addedPhoneNumberBox);
    }
});

window.addEventListener("resize", function() {
    if (window.innerWidth >= 850) {
            addPhoneNumberBox();
    };
});

Edit: Sorry should have explained why your script fails.

This line

const addedPhoneNumberBox = document.querySelector("#phoneNumberBox");

You are searching the dom for #phoneNumberBox before you add it in the next three lines

if (window.innerWidth >= 850) {
    addPhoneNumberBox();
}

so addedPhoneNumberBox isn’t found and is presumably a null. This is then going to fail

addedPhoneNumberBox.parentElement.removeChild(addedPhoneNumberBox);

First off you want to be doing this in CSS. I presume this is only a learning exercise with Javascript.

In fact if you were to use JS and CSS you would be better off just toggling a class on the phoneNumberBox element to show or hide it (see below)

Anyway I am going to follow your approach — all part of learning.

const headerContact = `
  <div id='phoneNumberBox' class='contactInfo'> 
      Call me: <a href='tel:+1 xxx-xxxxxx'>+1 012-3456780</a>
  </div>
`

const resizeHandler = function(event) {
    const header = document.querySelector('header')
    const contactInfo = header.querySelector('#phoneNumberBox')
    
    if (window.innerWidth > 850) {
        if (contactInfo === null) {
            header.insertAdjacentHTML('beforeend', headerContact)
        }
        return // exit out of the handler function
    }
    // if not already exited width is smaller than 850
    if (contactInfo !== null) contactInfo.remove()
}

window.addEventListener('resize', resizeHandler)
resizeHandler()

I’ve moved the contact template string into it’s own variable assignment. It’s now not tightly linked to .header-module--logo--B7flj which gives you a bit more flexibility and re-usage.

I have also created a separate handler function. This is instead of creating a function inside of the addEventListener. Just a bit clearer and easier to work with.

resizeHandler breakdown

finding and assigning elements
With querySelector we get the header element and assign to the header variable.

Then we search from that header element for #phoneNumberBox element and assign it to contactInfo.

innerWidth > 850
We check if the innerWidth is larger than 850

Note: querySelector returns either an element or if not found null.

Checking to see if contactInfo is null we can see if it already exists — we don’t want to continuously be adding contact infos to the end of the header.

If contactInfo doesn’t exist then add it with insertAdjacentHTML

We then exit out of the function with a return statement

innerWidth < 850
If we haven’t already exited in the last step we know the width is smaller than 850

This time we check to see if contactInfo isn’t null !== null. Which means contactInfo exists and so remove it.

Again this really isn’t the way to do this

Toggling

Here’s a preferrable option toggling a class of ‘hide’ instead. No templates and quite a bit simpler.

const resizeHandler = function(event) {
    const contactInfo = document.querySelector('#phoneNumberBox')
    
    contactInfo.classList.toggle('hide', window.innerWidth < 850)
}

Hello @rpg_digital !
Thanks for sharing from your experience.

Okay, I understand my mistake in trying to select an element that has yet to have been created (I thought it could be done “anteroactively” but okay).


I understand that I have another problem in the code — an “endless” creation of the element after largescale-resize:

window.addEventListener("resize", function() {
    if (window.innerWidth >= 850) {
            addPhoneNumberBox();
    };
});

Might this be a bug in JavaScript that there isn’t harmony between this block and the previous block (which deletes the element in smallscale-resize)?

I don’t think a bug.

I would say creating two separate eventListeners for resize is not the way to go in this instance. Keep things simple, one handler to deal with resizing here is good enough.

Your initial handler can always farm out tasks to separate functions if need be.

1 Like

Wouldn’t this be a case for matchmedia rather than clogging up the browser with endless resize events?

Roughly:

Or simply add the element by default with JS and then use css media queries to hide and show it.

1 Like

I would say creating two separate eventListeners for resize is not the way to go in this instance. Keep things simple, one handler to deal with resizing here is good enough.

I believe that the approach I took is simple, due to it’s direct modularity (no-kinship-modularity):

  • One procedure for initial adding of an element in desktop display
  • One procedure to remove it in any respective-smallscale-resize
  • One procedure to add it again in any respective-largescale-resize

Hello PaulOB,

Wouldn’t this require the existence of a media query with the exact value of 850px?

In this particular case, I seek a “loose” approach without being dependent in any already existing CSS data.

There is no css in my example.:slight_smile: You can see the codepen works well as it is.

Matchmedia is the js equivalent of css media queries. It does not rely on the resize event.

Thanks, I thought it is assumed that the webpage already contains it.

1 Like

New one to me Paul, will have a look into that myself, thanks. I was thinking the typical route of debounce.

1 Like

You’re dealing with the event loop aren’t you? The event loop has a lot to handle dealing with macro and micro task queues etc. everything from clicks to promises etc. The more handlers you have the more queued up processes to handle.

That’s why I prefer event delegation over numerous eventListeners.

Maybe I’m wrong, maybe someone else here has more to say.

edit: sorry for being curt, need to head out :slight_smile:

Thanks a lot.

I am not a native speaker of English, I try my best.

1 Like

If taking my original approach, I have found this code to work when testing in Google Chrome and in Microsoft Edge:

function addPhoneNumberBox() {
    document.querySelector(".header-module--logo--B7flj").insertAdjacentHTML('afterend', `
        <div class="phoneNumberBox" dir="ltr" style="text-align: center; background: red;">
            <h2>Call me:</h2>
            <a tel:X>+1 012-3456780</a>
        </div>
    `);
};

if (window.innerWidth >= 850) {
    addPhoneNumberBox();
}

window.addEventListener("resize", function() {
    if (window.innerWidth < 850) {
        document.querySelector(".phoneNumberBox").remove();
    }
});

window.addEventListener("resize", function() {
    if (window.innerWidth >= 850) {
        const [...PhoneNumberBox] = document.querySelectorAll(".phoneNumberBox");
        PhoneNumberBox.forEach( (element)=>{
            element.remove();
        });
        addPhoneNumberBox();
    };
});

Main differences from the original code:

  • I directly select and remove the phone number box after calling the function which creates it — by respective-smallscale-resizing handler
  • I have added a “cleaner” component which removes any phone number box (of that kind) which might have stayed existing — by respective-largescale-resizing handler

Looked at your codepen, that’s really good, and the event only fires on the breakpoint, so no debounce necessary.

One thing that did come up though is addListener, apparently it is now depreciated.

I believe you should use this instead

mqls.addEventListener('change', mediaqueryresponse);

Great stuff :+1:

1 Like

Bearing in my new my JS is very basic that looks about the worst way you could do this. I imagine that your page would be greatly impacted by the multiple resize calls.

MatchMedia is more efficient and only gets triggered once and not thousands of times.

@rpg_digital could confirm but I suspect that at the very least you would need to debounce the resize event to avoid any lag on the page

1 Like

Thanks. I copied the code from an old demo of mine so was unaware of the change :blush:

1 Like

I did make this codepen

@bendqh1 you will need to open the console at the bottom. Note how many times the resize event fires compared to matchmedia. matchmedia is more efficient and frees up the event loop to handle other things.

@PaulOB, @rpg_digital I try to follow the approach you share here but I come across a problem.

I tried the following code on sitepoint.com which fails, the contact box isn’t added:

const mqls = window.matchMedia("(max-width: 850px)");
const hostElement = document.querySelector(".header-module--logo--B7flj");
const parasiteElement = `
    <div class="phoneNumberBox" dir="ltr" style="text-align: center; background: red;">
        <h2>Call me:</h2>
        <a tel:X>+1 012-3456780</a>
    </div>
`

function mediaqueryresponse(mql) {
    if (mqls.matches) {
        hostElement.insertAdjacentHTML("afterend", parasiteElement);
    };
};

mediaqueryresponse(mqls);

You missed out the important bit which was the event listener that listens for the media query breakpoint and you seem to be trying to insert the element into an svg instead of the header.

const mqls = window.matchMedia("(max-width: 850px)");
const hostElement = document.querySelector(".header-module--nav--7IzCJ");
const parasiteElement = `
    <div class="phoneNumberBox" dir="ltr" style="text-align: center; background: red;">
        <h2>Call me:</h2>
        <a tel:X>+1 012-3456780</a>
    </div>
`;

function mediaqueryresponse(mql) {
  if (mqls.matches) {
    hostElement.insertAdjacentHTML("afterend", parasiteElement);
  }
}

mediaqueryresponse(mqls);

// attach listener function to listen in on state changes
mqls.addEventListener("change", mediaqueryresponse);

Thanks, PaulOB.

Something interesting happens, the code worked for me without problem when I started from mobile display,
But when I changed max-width to min-width (which is what I personally need), than the code worked only partially — the contact box appeared both in mobile and desktop displays.

(tested on sitepoint.com from Google Chrome on Windows 10 from my laptop).