Join me in this post as I use SVG (Scalable Vector Graphics) sprites in a React application.
Sometimes you find the need to introduce SVGs into your application. It's usually done for having vector graphic icons, but for me the need came from a different direction.
You see, in my Word Search game there are letters scattered around and the player needs to combine these letters to form up words, but there is a way they can make it a bit easier for them - they can search the letters using the browser's “find in page” and get the entire locations of a specific letter. From there it is much simpler.
I wanted to disable this, and the idea which came to my mind was not using “real” characters but “icons” which represent these letters. SVG seemed to be suitable for this purpose, but I was not going to fetch each letter on its own - SVG sprites came to solve exactly that!
First step is to get SVG for the entire English alpha-bet. I found this nice tool which converts Google fonts to SVG paths, and I used it to generate the needed letters and put them all in an .svg
file I called letters.svg
.
A sample of letter path would look like this:
<path d="M 11.631 17.578 L 17.959 17.578 A 1.331 1.331 0 0 0 17.937 17.323 Q 17.909 17.184 17.849 17.084 A 0.507 0.507 0 0 0 17.49 16.846 A 1.49 1.49 0 0 0 17.197 16.816 L 16.201 16.816 L 9.492 0 L 8.408 0 L 1.846 16.816 L 0 16.816 L 0 17.578 L 5.215 17.578 A 1.331 1.331 0 0 0 5.192 17.323 Q 5.165 17.184 5.105 17.084 A 0.507 0.507 0 0 0 4.746 16.846 A 1.49 1.49 0 0 0 4.453 16.816 L 3.018 16.816 L 5.215 11.133 L 11.338 11.133 L 13.535 16.816 L 11.631 16.816 L 11.631 17.578 Z M 8.262 3.193 L 11.016 10.283 L 5.537 10.283 L 8.262 3.193 Z" vector-effect="non-scaling-stroke"/>
Don’t forget I wanted this file to act as a sprite and therefore each letter should have an identifier by which I can grab only it out of the entire file. For this we add the <simbol>
tag which may look like this:
<symbol id="letter-a" viewBox="-6 -6 30 30">
... the SVG path for A
</symbol>
You are probably wondering what these numbers are on the viewBox.
The “viewBox” allows me to define the dimensions and position of the SVG viewport, which simply put, means that I can set the width and height along with x and y coordinates of positioning.
In the example above, and since I’ve generated the fonts in a certain size (30px) these are the values to use in order to center the letter in a 30 by 30 pixels div (but different letter sometime have different dimensions so this is something that you would probably need to tweak later).
Ok, so now that we got the sprite ready to be used, how do we use it?
For this we will create a React function component which represents the letter:
import React from 'react';
import Letters from '../letters.svg';
import './LetterSvg.scss';
const LetterSvg = ({letter, color, size}) => (
<svg className="svg-letter" fill={color} width={size} height={size}>
<use href={`${Letters}#letter-${letter}`} />
</svg>
);
LetterSvg.defaultProps = {
size: 30,
color: 'black',
};
export default LetterSvg;
What we have here is a Component which represents an SVG element. It receives 3 props - the letter we want to display, the color of it and the size. The size and color have default values, but obviously can be set from outside as well.
(The <use>
tag, as the documents so well describe it, takes nodes from within the SVG document, and duplicates them somewhere else.)
Now, in order to use this component for each letter I will change the code which previously just placed the letter char as a child, into using the Letter component as one:
<div . . .>
<LetterSvg letter={letter} />
</div>
Great! Now the letters grid displays my SVG letters instead of “real” chars I can go and collect the letters and… wait. I cannot select the letters. Why?
Well, when I previously had a char as the letter, the event target I’m using when mouse is down was LetterBox, but now the child has changed into a more complex component, and so the target has become the SVG or the <use>
inside of it.
No worries, let’s disable any mouse events on the svg element or its children by adding the following CSS rule to the LetterSvg.scss:
.svg-letter {
pointer-events: none;
}
Done. Letters can now be collected!
One more thing to take care of and that’s the style behavior when the letter is marked as collected. In the past it used to set the letter color to white, but how do we do this now?
You’ve probably guessed it - we’re changing the color prop of the LetterSvg component according to that state:
<LetterSvg letter={letter} color={collected ? 'white' : ''} />
And that’s it basically.
So to recap - we achieved our goal which was not to allow a simple browser find to locate the letters in the grid. For that we used an SVG sprite, to avoid fetching multiple letters each on their own, and wrapped it with a simple component. Awesome :)
As always, if you have other techniques in achieving the same goal, or any comments on how this can be improved, be sure to share with the rest of us.
Hey! If you liked what you've just read check out @mattibarzeev on Twitter 🍻
Photo by Brett Jordan on Unsplash