Here's a fun one. I was removing lifestyle images from a product page slider (Slick Slider), and on desktop it worked great. On mobile emulators, also great. On an actual physical device, the images disappeared but the slides stayed, just sitting there empty.
Ghost slides.
What was supposed to happen
The goal was pretty simple: find images matching a lifestyle pattern (like LS-1234.jpg), remove them from the carousel, and reinitialize Slick so the layout adjusted. A few lines of code.
The code was doing four things:
- Finding images matching the pattern
- Removing them from the DOM
- Calling
slickRemoveon the matching slides - Destroying and reinitializing Slick
On desktop and emulators it worked. On a real phone, I got hollow slides with nothing in them.
What didn't work
I tried a few things before landing on the fix.
Just calling .remove() on the <img> elements: the images disappeared, but Slick's internal state still thought those slides existed.
Using .slickRemove(index): worked sometimes on mobile, not reliably.
Calling .slick('setPosition'): did nothing about the extra slides.
The confusing part was that the DOM inspector looked fine. Slick just maintains its own internal slide count separately, and it wasn't letting go of the old state.
What actually worked
The fix was a three-step cleanup.
1. Remove images and their wrappers
Instead of targeting just the <img>, I went up to the parent .slick-slide and removed the whole wrapper:
const removeLifestyleImages = async (container) => {
if (!container) return;
const regex = /(LS-\d+|LS\d+|alt\d+|alt-\d+|lifestyle\d+)$/i;
container.querySelectorAll('img').forEach((img) => {
if (regex.test(img.src)) {
let wrapper = img.closest('.slick-slide');
if (wrapper) {
wrapper.remove();
}
}
});
};2. Clean up any remaining empty slides
Even after that, sometimes an orphaned .slick-slide would hang around with no content. Added a pass to catch those:
const manuallyRemoveExtraSlides = () => {
document.querySelectorAll('.slick-slide').forEach((slide) => {
if (!slide.innerHTML.trim()) {
slide.remove();
}
});
};3. Destroy and reinitialize with a delay
Slick needs a moment. If you reinitialize immediately after modifying the DOM, it doesn't always catch up. A 300ms delay before reinitializing fixed the remaining weirdness:
const resetSlick = () => {
const slickContainer = $('.product-image');
if (slickContainer.hasClass('slick-initialized')) {
slickContainer.slick('unslick');
}
setTimeout(() => {
slickContainer.slick({
infinite: true,
slidesToShow: 1,
slidesToScroll: 1,
dots: true,
arrows: false,
});
}, 300);
};Putting it all together
const hideLifestyleImages = async () => {
const productMedia = document.querySelector('.product.media');
await removeLifestyleImages(productMedia);
manuallyRemoveExtraSlides();
resetSlick();
};
hideLifestyleImages();What I took away from this
Always test on a real device. Emulators are close, but mobile Chrome behaves differently enough from desktop that you can have a feature that looks done and isn't. Literal edge case, lol.
Also: Slick tracks its own state separately from the DOM. If you modify slides manually, you kind of have to brute-force it into updating by destroying and rebuilding the whole thing. A little annoying, but reliable once you commit to it.
No more ghost slides.