Skip to content

Commit

Permalink
Merge pull request #54 from allegro-internal/icons-2024
Browse files Browse the repository at this point in the history
Web icons 2024
  • Loading branch information
mkosmul authored Jan 10, 2024
2 parents c409968 + 918a481 commit 9cd4c11
Show file tree
Hide file tree
Showing 8 changed files with 227 additions and 1 deletion.
6 changes: 6 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ GEM
ruby2_keywords (>= 0.0.4)
faraday-net_http (3.0.2)
ffi (1.15.5)
ffi (1.15.5-x64-mingw-ucrt)
ffi (1.15.5-x64-unknown)
forwardable-extended (2.6.0)
gemoji (3.0.1)
Expand Down Expand Up @@ -218,6 +219,8 @@ GEM
racc (~> 1.4)
nokogiri (1.13.10-arm64-darwin)
racc (~> 1.4)
nokogiri (1.13.10-x64-mingw-ucrt)
racc (~> 1.4)
nokogiri (1.13.10-x86_64-darwin)
racc (~> 1.4)
octokit (4.25.1)
Expand Down Expand Up @@ -257,13 +260,16 @@ GEM
unf (0.1.4)
unf_ext
unf_ext (0.0.8.2)
unf_ext (0.0.8.2-x64-mingw-ucrt)
unf_ext (0.0.8.2-x64-unknown)
unicode-display_width (1.8.0)
wdm (0.1.1)
webrick (1.8.1)
zeitwerk (2.6.7)

PLATFORMS
arm64-darwin-22
x64-mingw-ucrt
x64-unknown
x86_64-darwin-21

Expand Down
5 changes: 5 additions & 0 deletions _data/members.yml
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ michal.jezierski:
pawel.lesiecki:
name: Paweł Lesiecki
bio: Paweł is a full-stack software engineer and an enthusiast of new technologies.
twitter: liseuek

alicja.antkowiak:
name: Alicja Antkowiak
Expand Down Expand Up @@ -889,3 +890,7 @@ mateusz.kuzmik:
bio: I just like to know how things work. On a daily basis, I spend a lot of time with JVM Tech.
linkedin: matkuzm
github: mkuzmik

maciej.suszko:
name: Maciej Suszko
bio: A Senior Software Engineer at Allegro working on our internal Metrum Design System. An enthusiast of automation and innovation. Fueled by a passion for 3D modeling and tinkering with game engines.
2 changes: 1 addition & 1 deletion _posts/2023-01-25-lazy-loading-with-mbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ And the second part would load when the user scrolls to the end of the first par
This technique is called lazy loading.
It is not something new, it is used web-wide in many places, but in our case, we had to think about it differently because …

... we built the Allego homepage in applications using **MBox**, the server-driven UI solution created at Allegro, which means that the content and screen logic is defined entirely on the server side.
... we built the Allegro homepage in applications using **MBox**, the server-driven UI solution created at Allegro, which means that the content and screen logic is defined entirely on the server side.
Implementation of lazy loading for the Allegro homepage also had to be done on the server side.

> What is **MBox**? It is our **Server-Driven UI (SDUI)** solution, which we use at Allegro to create and release mobile screens faster on both platforms (iOS and Android). It is a collection of building blocks that let us develop views and actions that link MBox screens with other parts of the application or introduce some interaction on a screen.
Expand Down
205 changes: 205 additions & 0 deletions _posts/2024-01-10-embed-multicolor-icons-using-a-single-DOM-element.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
---
layout: post
title: "Embed multicolor icons using a single DOM element"
author: [pawel.lesiecki, maciej.suszko]
tags: [tech, web, svg, css, html]
---
Hello, fellow Web developers!

Icons are an integral part of most modern UIs.
What is the best way to embed icons nowadays?
This area is full of pitfalls.
You better proceed with caution when trying to answer that question.

Though there are many possibilities, [some of which are considered harmful](https://twitter.com/_developit/status/1382838799420514317).
Various inline SVG techniques have become more popular over time. Possibly due to the lack of suitable alternatives, although not using the cache is a huge trade-off.
Thankfully, [there are some voices of reason](https://twitter.com/getifyX/status/1720810762409566459) in the community.

At [Allegro](https://allegro.tech/), we’ve been using SVG and CSS filters for quite some time.
However, they have their limits and could be better suited for the challenges of the themeable design system.

Let’s pause for a moment and rethink the approach to icons.
It has to meet several requirements:
- themeable,
- cacheable,
- easily embeddable.

## Can we do better than we’ve been doing so far?

![Multilayer icon](/img/articles/2024-01-10-embed-multicolor-icons-using-a-single-DOM-element/icon.webp "Multilayer icon")

All the tools needed to perform the trick have been available in major browsers for at least few years.
Is it possible everyone just failed to connect the dots?
It turns out that the platform is capable of dealing with icons more efficiently.

**Let us introduce the SVG+CSS technique. It lets you have a 3-color icon using just one DOM element and one external SVG.**

We have found nothing similar, whether online or with ChatGPT, which makes us want to share this idea with you even more.

Consider the proposed technique if you care about performance.

### Key benefits are:

1. Caching.
2. Works cross-domain.
3. Customizable more than a single color.
4. Icons load after critical resources and content, not bloating the markup.

We will control the colors of 3 different parts with a single DOM element and SVG resource.

Sounds interesting? Then, let’s dive into how we can accomplish this.

## Implementation

SVG and CSS are gifts that keep giving and can do wonders combined.

The proposed technique is a combination of two platform capabilities.
1. [SVG Fragments](https://css-tricks.com/svg-fragment-identifiers-work/)
2. [CSS Masks](https://developer.mozilla.org/en-US/docs/Web/CSS/mask-image)

[SVG Fragments aren’t really a new technology](https://caniuse.com/svg-fragment).
About five years ago, we considered using CSS masks, but we still supported IE back then.
At that time, we had not yet thought of combining it with fragments.

As a case study let’s pick one of our icons —
<img class="inline-image" alt="a-icon" src ="https://a.allegroimg.com/original/34412f/ae71613e49d986c5c838698e2e86/illustration-allegro-in-circle-big-db0c91e439"/>.

With the following source:

```svg
<svg viewBox="0 0 32 32" fill="none" height="32" width="32" xmlns="http://www.w3.org/2000/svg">
<path d="..." fill="#B0B8BC"></path>
<path clip-rule="evenodd" d="..." fill-rule="evenodd" fill="#FF7B33"></path>
<path d="..." fill="#D9DFE4"></path>
</svg>
```

This particular icon consists of 3 parts, each with a different color.
Now, we’re going to control these colors with the document’s CSS.

It’s time to program in SVG and CSS for a moment.

### Step #1 — SVG Fragments

First, let’s craft our test subject and introduce the fragments.
Each `path` gets a unique Fragment Identifier by setting an `id` attribute.
Next, we add a little CSS to enable rendering fragments in isolation. Think of it as an image sprite.

For the sake of CSS simplicity, we also group all the paths under an extra `g` element with a unique `id`.

As a result, the SVG is supposed to look like this:

```html
<svg viewBox="0 0 32 32" fill="none" height="32" width="32" xmlns="http://www.w3.org/2000/svg">
<style>
path:not(:target) {
display: none;
}
g:target path {
display: inline;
}
</style>
<g id="icon">
<path id="border" d="..." fill="#B0B8BC"></path>
<path id="a" clip-rule="evenodd" d="..." fill-rule="evenodd" fill="#FF7B33"></path>
<path id="shadow" d="..." fill="#D9DFE4"></path>
</g>
</svg>
```

It produces 4 fragments, one for each of the three paths and the last for the whole icon. Each of them can now be rendered as a separate image:

1. [`#a`](https://a.allegroimg.com/original/34901c/db3b33c5488eb13bc5244e215953/illustration-allegro-in-circle-big-ab3336c0b3#a) — <img class="inline-image" alt="#a" src="https://a.allegroimg.com/original/34901c/db3b33c5488eb13bc5244e215953/illustration-allegro-in-circle-big-ab3336c0b3#a"/>
2. [`#border`](https://a.allegroimg.com/original/34901c/db3b33c5488eb13bc5244e215953/illustration-allegro-in-circle-big-ab3336c0b3#border) — <img class="inline-image" alt="#border" src="https://a.allegroimg.com/original/34901c/db3b33c5488eb13bc5244e215953/illustration-allegro-in-circle-big-ab3336c0b3#border"/>
3. [`#shadow`](https://a.allegroimg.com/original/34901c/db3b33c5488eb13bc5244e215953/illustration-allegro-in-circle-big-ab3336c0b3#shadow) — <img class="inline-image" alt="#shadow" src="https://a.allegroimg.com/original/34901c/db3b33c5488eb13bc5244e215953/illustration-allegro-in-circle-big-ab3336c0b3#shadow"/>
4. [`#icon`](https://a.allegroimg.com/original/34901c/db3b33c5488eb13bc5244e215953/illustration-allegro-in-circle-big-ab3336c0b3#icon) — <img class="inline-image" alt="#icon" src="https://a.allegroimg.com/original/34901c/db3b33c5488eb13bc5244e215953/illustration-allegro-in-circle-big-ab3336c0b3#icon"/>

Now, the regular fragment-less URL will display a blank image.
Thus, for the full icon, we’re going to add a [`#icon`](https://a.allegroimg.com/original/34c91a/651290b94002acbe836ae520e8ff/illustration-allegro-in-circle-big-ab3336c0b3#icon) fragment to the URL.

We won’t use the `#a` fragment, but let’s keep its identifier.

### Step #2 — CSS Masks

With SVG Fragment Identifiers up and ready, we can use CSS Masks.

The base class `.icon` stacks three layers on top of each other, ready for a mask.

```css
.icon {
position: relative;
width: 32px;
height: 32px;
mask-repeat: no-repeat;
background-color: currentColor;
}
.icon::before,
.icon::after {
content: '';
position: absolute;
width: inherit;
height: inherit;
background-color: inherit;
}
```

The last CSS class is for our specific icon.

```css
.icon--a {
mask-image: url('./a.svg#icon'); /* full icon’s shape */
color: #FF7B33;
}
.icon--a::before {
mask-image: url('./a.svg#shadow'); /* shadow’s shape */
color: #D9DFE4;
}
.icon--a::after {
mask-image: url('./a.svg#border'); /* border’s shape */
color: #B0B8BC;
}
```

The critical part is that we picked the whole icon, not any fragment, as the parent’s mask, so we have the entire icon visible.
That’s because the parent layer masks its children.

We selected the orange color of the `a` for the parent layer.
Then, we put the two remaining layers on top of it.
The second and third layers are for shadow and border parts, respectively.

**We can describe this as the whole icon in single color covered by one or more shapes in different colors.**

When an icon has intersecting parts, there’s one thing to keep in mind.
Backgrounds render on top of each other in a particular order:
1. the parent’s background,
2. the `::before` pseudo-element’s background,
3. the `::after`’s background at the end.

As a result, we can embed the icon by a single element.

```html
<div class="icon icon--a" aria-hidden="true"></div>
```

Let’s also consider accessibility.
Usually, icons are [purely decorative content](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-hidden#description).
That’s why we remove them from the accessibility tree by `aria-hidden="true"`.

The result is supposed to look like the original icon from the start —
<img class="inline-image" alt="the original icon from the beginning" src="https://a.allegroimg.com/original/34412f/ae71613e49d986c5c838698e2e86/illustration-allegro-in-circle-big-db0c91e439"/>.

Now, the single element gives us control over up to three parts of our icon.
Moreover, we can change colors independently and dynamically.

## The demo
Feel free to check the [demo](https://three-colors-one-element-icon.plesiecki.repl.co/).

Pretty neat.

We found this technique practical, and we’re keen to use it in the future.

## More colors
If you need more than 3 colors, switch from pseudo-elements to regular elements. Then, you can stack as many layers as you want.
Another option is to combine `background-image` with gradients instead of `background-color`.

Enjoy & use the platform ❤️
4 changes: 4 additions & 0 deletions authors/maciej.suszko/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
layout: author
author: maciej.suszko
---
6 changes: 6 additions & 0 deletions css/blog.scss
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,12 @@ figcaption {
}
}

.post-content img.inline-image {
display: inline;
vertical-align: middle;
margin: auto;
}

.post-content blockquote {
border-left: 4px solid #e8e8e8;
padding-left: 20px;
Expand Down
Binary file not shown.
Binary file added img/authors/maciej.suszko.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 9cd4c11

Please sign in to comment.