Back to articles
July, 28th 2022

Background-aware swiper pagination

The problem

Some parts of user interface are not background aware, however are meant to be. It’s especially noticeable when working with user-generated content that is different in colors, exposure and size. Take gallery dot pagination for example below:

Pagination dots on the left screen have good contrast with a grey background and hand skin color. However, changing to the second image in a gallery destroys all the contrast and the pagination becomes completely unreadable.

In the parts of the interface where it is hard or impossible to predict the color theme of the background content, there are limited options to tackle this problem.

iOS solves similar problem at home-screen pagination and custom user backgrounds.

It appears that is detects the image theme and sets dots white or black color accordingly. From what I can tell the detection happens for a certain area of the image (or a total amount of dark/light colors), so images of inconsistent fill produce weird results.

Can we achieve something like this on the Web? Something, that will work almost regardless of the image theme and won’t require image analysis beforehand? I was surprised, but… CSS.

The solution

It came to me during lunch: invert() backdrop-filter. It is doing exactly what we need — inverts a color of a background. After playing with it for some time, it found out it is doing quite good even at a first try.

/* html */
...
<div class="gallery__dots">
  <div class="gallery__dot gallery__dot--active" />
  <div class="gallery__dot" />
  <div class="gallery__dot" />
</div>
...

And CSS:

/* css */
.gallery__dots {
  display: flex;
}

.gallery__dot {
  width: 8px;
  height: 8px;
  border-radius: 100%;
  margin: 4px;
  opacity: 0.3;
  backdrop-filter: invert();
  transition: opacity 200ms ease-in-out;
}

.gallery__dot--active {
  opacity: 1;	
}

Check out example implementation here.

How can we improve?

While this filter gives us great behaviour out of the box, there are things to improve. When using different images, we’ll see that some visual bugs stand out, check this example:

On intersection of light and dark areas out naturally reflects it, but it’s not desired in our case. Also the contrast could be improved. Both these issues can be targeted with the following code:

/* css */
.gallery__dot {
  /* ...rest of the properties */

  backdrop-filter: blur(100px) grayscale() contrast(800%) invert();
}

.gallery__dot--active {
  opacity: 1;
}

Here we added blur as a first filter, to avoid intersections of light and dark areas; grayscale filter helps us to to reduce colors — we just need the dot to be either with or black; increased contrast (a lot haha) — it helps to convert neutral tones to more have contrast. Let’s see what we have now:

One more thing

Since we apply backdrop-filter to every dot individually, this happens when you scroll over spotty backgrounds:

Ideally, first black dot should have already switched to white, as other faded-out ones. Small CSS improvement could get us closer to that goal:

/* css */
.gallery__dots {
  transform: scale(4) translateX(-2px);
}

.gallery__dot {
  /* ...rest of the properties */

  width: 2px;
  height: 2px;
  backdrop-filter: blur(100px) grayscale() contrast(800%) invert();

  /*
    use translateX instead of margin, so backdrop-filter
    would render closest background possible for each dot
  */
  transform: translateX(var(--dot-index) * 2px);
}

.gallery__dot--active {
  opacity: 1;
}

The idea here is to put our dots in minimum size and as close as possible to each other. Then, we can scale all of them through the parent element, and put additional space in between through individual translateX. This way all of them will share way more common background and will render more consistent UI.

Take into account that Firefox only recently added full support for backdrop-filters and might need a fallback.


Other Posts
March, 31st 2022 Tree-shaking Vuex State in Nuxt Document response The way Nuxt delivers state data from server to client can play a bad joke with your loading performance. All of your Vuex state data on the server will be printed in the response Document as JS objec..
February, 23rd 2022 Better DOM Debugging With Component Name in HTML Attribute Sometimes the key of keeping great DX is in tiny details. Debugging markup of modern frontend might be very scary these days. With atomic CSS classes getting trendy, the DOM in devtools becomes harder..