Open 9am opened 7 months ago
A couple of weeks ago, a bug was reported about a React component owned by my team, which is a ToggleButton
that works like the native <input type="radio">
group. Except that a colored highlight will follow the active item with a transition
effect as the users select.
The bug is:
The highlight does not resize and reposition when
textContent
changes happen on the active item.
Well, the cause is the original implementation of the active highlight:
size
and position
as the active item.size
and position
when the active item changes, which will cover most cases but not DOM mutation.After digging a little bit deeper, I found that a universal solution is harder than I thought.
𐄡
## The problem ␥
Let's look at what we're dealing with:
> 1. For a `source ` element (A) in any layout on the web page, create a `highlight` element (B) that follows the size and position of (A) no matter how the page changes.
> 2. If the `source` switches from (A) to (C), (B) will switch source accordingly.
It can be split into 3 questions:
1. **Where to put the `highlight`?**
2. **How to get the new position and size of the `source`?**
3. **When to trigger rerender?**
𐄡
## A simple case ␥ To answer those questions, let's start with a simple case. Here we have a horizontal flex layout of items with the same sizes. ![s-3](https://github.com/9am/9am.github.io/assets/1435457/de574f1c-f0ca-4975-a8c9-18624b0c51c5) > 1, Where to put the `highlight`? To make it transition, we can add a new element(or pseudo-elements) in the `container` and make it `position: absolute`, so that changes outside `container` can not affect how it is positioned. ```html
𐄡
## The universal solution ␥
After asking those 'what ifs', I found myself end up trying to figure out how the browser layout elements and what triggers the layout change. So a once-for-all solution looks like this:
### The final answers
1. Where to put the `highlight`?
> New Element or Pseudo Element.
2. How to get the new position and size of the `source`?
> `getClientBoundingRect()`.
3. When to trigger rerender?
> `container` resize & mutate
> `active item` resize
> ![s-11](https://github.com/9am/9am.github.io/assets/1435457/bb796045-6e9e-4d77-b41b-2b38b16ea870)
>
> [**live demo**](https://developer.mozilla.org/en-US/play?id=H1RisSObxW0e5aJavQdI5iTRBPa9nKV2SPeKcz00BXn0yeChPMTAlhhg%2BCYABo%2BJRWVR7ne67ZS%2BhIy4)
And I created a custom React Hooks called [use-spotlight](https://github.com/9am/use-spotlight) for this kind of situation, which involves other problems to solve like the updating of active `refs` won't trigger a rerender, etc. Anyway I managed to do it, but the code gets more complicated.
I can't help but wonder: Does it need to be this hard to do a simple effect like this on the web? What is the missing block here?
𐄡
## New option: view transition API ␥
Creating an element with the same size and position as another element is easy on the web. An absolute positioned 100% width height child will always follow the source. Why we have this solution above is that **we couldn't do a transition between different elements**. So we need to stick with 1 element and calculate the size and position manually.
The `view transition API` came up to smooth the transition experience for the SPA situation. But it definitely can do more. **It brings transition between different elements to the web for the first time**. Check this out:
> ![s-12](https://github.com/9am/9am.github.io/assets/1435457/b5d492b3-83d6-426f-971c-2a9025100f19)
>
> [**codepen**](https://codepen.io/9am/pen/QWYJJEr)
Since the 'highlight' is a pseudo, even without the `startViewTransition()`, it will still be working as a 'fallback' version of 'highlight'. That's a wonderful way of **treating 'transition' as an enhancement**.
> [!NOTE]
>
> This is an experimental technology
> Check the [Browser compatibility table](https://developer.mozilla.org/en-US/docs/Web/API/ViewTransition#browser_compatibility) carefully before using this in production.
𐄡
## Closing thoughts
Web development evolved so fast for years and the trend is not slowing down. New things keep coming up about HTML/CSS/JS, frameworks, bundlers, etc... I'm coding in a way totally different from 5 years ago, even 1 year ago. In a way that's a good thing, but the concepts are getting more and more complicated. And one needs to learn a ton of knowledge ahead to start building something or understand what's happening behind the codes. I kind of miss the simplicity of the old-time web. **At the end of the day, it's not technology that holds us back from sharing ideas**.
Or maybe I'm just getting old -_-|||
---
> ## @9am 🕘
> * Read more [articles](https://9am.github.io) at [9am.github.io](https://9am.github.io)
> * Find other [things](https://www.npmjs.com/search?q=%409am) I built on [GitHub](https://github.com/9am) and [NPM](https://www.npmjs.com/~9am)
> * Contact me via [email](mailto:tech.9am@gmail.com)
> * [![Creative Commons License](https://i.creativecommons.org/l/by-nc-nd/4.0/80x15.png)](http://creativecommons.org/licenses/by-nc-nd/4.0/)