When I updated the blog section of my portfolio site, I realized I had a great opportunity to draw some cute SVGs and take my loading and error messages to the next level.
When I started, the loading text looked like this:
and the error text looked like this:
I spent a handful of hours making my loading graphic:
and my error graphic:
and I'm ready to add them to my site!
For more information on how I made these Shiba Inu graphics, and how to find or make images of your own, check out my post, Sourcing Images and Optimizing Them for the Web.
Putting the SVGs in my React App
My complicated Shiba SVGs are a little big (30 - 35 KB), but I'm choosing an .svg
format over a .png
format for a few reasons:
- I just removed hundreds of KBs of photos from my app by rewriting my blog to pull from the DEV API
- the PNG compression only takes them down to 21KB
- ease of resizing
- animation
I start by making two new components - Error.js and Loading.js. My initial Error component looks like this:
import React from 'react';
function Error() {
return (
<div>
<p>There was an error! Try again later.</p>
</div>
)
}
export default Error
There are a few ways I can import my SVGs into my components. If I were using CDN or another image hosting tool, I could use an <img>
with a src
link like this:
<img src="https://someSvgLink.com/svgID" alt="very descriptive alt text" />
Or I could use an <img>
with the SVG saved and imported like a JPEG or PNG:
import React from 'react';
import ConfusedShiba from '../images/confused-shiba-inu.svg'
function Error() {
return (
<div>
<img src={ConfusedShiba} alt="Confused Shiba Inu"></img>
<p>There was an error! Try again later.</p>
</div>
)
}
export default Error
Neither of these would let me animate or change my SVGs programmatically. Pasting the code directly into the React component would allow me to do that, but the complexity of my Shiba SVG makes for very long code. Here's an example of a React wrapper for a very simple SVG from my site. The code is for the turning arrow in the 'view my work' button on my landing page and that <line>
was my first SVG code!
function Error() {
return (
<div>
<svg role="img" aria-labelledby="arrowTitle arrowDesc" fill='none' stroke='#0E1A27' stroke-width='8' stroke-dashoffset='0' stroke-dasharray='0' stroke-linecap='round' stroke-linejoin='round' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'>
<title id="arrowTitle">Arrow</title>
<desc id="arrowDesc">arrow that spins on hover</desc>
<line x1="70" y1="50" x2="0" y2="50" />
<polyline fill="none" points="30,10 80,50 30,90 "/>
</svg>
<p>There was an error! Try again later.</p>
</div>
)
}
Ultimately, I choose to take advantage of create-react-app
's option to import an SVG as a component. Translating SVGs to the DOM this way is expensive, but in this case, I have less than 10 SVGs on my site imported this way, and they're all in use. For a larger site, I would look into packages like SVGR, using the Webpack Asset Modules or an icon library like Font Awesome. My Error component now looks like this:
import React from 'react';
import {ReactComponent as ConfusedShiba} from '../images/confused-shiba-inu.svg'
function Error() {
return (
<div>
<ConfusedShiba />
<p>There was an error! Try again later.</p>
</div>
)
}
export default Error
And I import both my Error component and my very similar Loading component into my Blog component like this:
import Error from '../components/Error';
import Loading from '../components/Loading';
} else if (!state.isLoading && state.error) {
return (
<Error />
)
} else {
return (
<Loading />
)
}
}
Now my Shibas are in my site but because there's no styling, they're HUGE and still on the wrong background. Time to add some CSS. First, I know I'll want some padding and a max-width
rule for my Shibas.
/* error and loading graphics */
.error-graphic {
padding: 20px 20px 0px 20px;
max-width: 200px;
}
.loading-graphic {
padding: 20px 20px 0px 20px;
max-width: 200px;
}
200px is looking nice on my laptop screen, so that's probably about the largest I'll want to go. That means that I need to add in media queries and change the width in this rule to the smallest I'll go, as I used mobile-first design to make my site responsive down to 280px.
.error-graphic {
padding: 20px 20px 0px 20px;
max-width: 100px;
}
.loading-graphic {
padding: 20px 20px 0px 20px;
max-width: 100px;
}
@media screen and (min-width: 350px) {
.error-graphic, .loading-graphic {
max-width: 150px;
}
}
@media screen and (min-width: 525px) {
.error-graphic, .loading-graphic {
max-width: 200px;
}
}
Next, I add container styling to match the rest of my site, and the result looks like this:
But that's not all! I noticed that my error Shiba's question mark doesn't meet minimum contrast standards on my container background. Because this image doesn't add information to the page, it's not super important that it does, but I could animate it to cycle through many colors, at least one of which should meet standards. Plus, using CSS variables to change the color of my SVGs was one of my favorite parts of my Toggle Dark Mode In React post.
To do this, I'm going to have to edit my SVG code directly. First, using right click > inspect
in my Chrome browser, I find the <path>
s and the <circle>
I want to animate. Then, I add "questionMark", "questionMarkFill", and "questionMarkDot" id
properties to them, so I can always find them. Next, I remove the <style>
Illustrator created in my SVG with my colors and rewrite them into CSS rules. Both this SVG and my other Shiba Inu have the same Illustrator-named classes like "cls-1", so I also rename the classes. After a fair amount of Googling variations of "SVG CSS color rotate" and settling upon using @keyframes
and hsla()
, I found Dan Wilson's expert color math. Using that, I write two CSS animations to cycle through colors gradually:
@keyframes colorRotate-fill {
0%, 100% {
fill: hsl(0,100%,50%);
}
10% {
fill: hsl(36,100%,50%);
}
20% {
fill: hsl(72,100%,50%);
}
30% {
fill: hsl(108,100%,50%);
}
40% {
fill: hsl(144,100%,50%);
}
50% {
fill: hsl(180,100%,50%);
}
60% {
fill: hsl(216,100%,50%);
}
70% {
fill: hsl(252,100%,50%);
}
80% {
fill: hsl(288,100%,50%);
}
90% {
fill: hsl(324,100%,50%);
}
}
@keyframes colorRotate-fill-hsla {
0%, 100% {
fill: hsla(0,100%,50%,.2);
}
10% {
fill: hsla(36,100%,50%,.2);
}
20% {
fill: hsla(72,100%,50%,.2);
}
30% {
fill: hsla(108,100%,50%,.2);
}
40% {
fill: hsla(144,100%,50%,.2);
}
50% {
fill: hsla(180,100%,50%,.2);
}
60% {
fill: hsla(216,100%,50%,.2);
}
70% {
fill: hsla(252,100%,50%,.2);
}
80% {
fill: hsla(288,100%,50%,.2);
}
90% {
fill: hsla(324,100%,50%,.2);
}
}
The 'a' in 'hsla' represents transparency, where 0 is completely transparent and 1 is completely opaque. The way I made my SVG requires a third rule called colorRotate-stroke
which looks exactly like colorRotate-fill
, but with the property stroke
instead of the property fill
. Then I apply the rules to the appropriate new classes:
.error-graphic_qm_dot {
fill:#a19ece;
stroke:#524f77;
stroke-miterlimit:10;
stroke-width:3px;
animation-name: colorRotate-stroke, colorRotate-fill-hsla;
animation-duration: 3000ms;
animation-fill-mode: forwards;
transition: all 360ms ease-in;
animation-iteration-count: infinite;
}
The result looks like this:
A note on SVG accessibility
In doing research on SVGs, I found the recommended tags to include to make SVGs accessible. I've updated all the svgs on my site to have a <title>
, <desc>
, and the role="img"
and aria-labelledby="titleID descID"
properties in the <svg>
tag.
While looking into animating SVGs, I saw color animation could trigger some accessibility issues, so I applied a rule I learned about in my demo of the updated blog portion of my site:
@media (prefers-reduced-motion) {
.error-graphic_qm-fill, .error-graphic_qm-lines, .error-graphic_qm-lines {
animation-name: dissolve;
}
}
Conclusion
I really enjoyed making and styling these Shiba Inu SVGs. For an in depth breakdown of image types for the web and how to make or find your own for free, check out this article's sister blog, Sourcing Images and Optimizing Them for the Web. You can check these Shibes out live at abbeyperini.dev and see all the code for my site in the repo.