Even though IE 11 still struggles a bit with properly scaling SVG files, SVGs are now supported by all modern browsers. That is, at least according to Can I Use. To cite Jake Giltsoff:
Scalable Vector Graphics can look crisp at all screen resolutions, can have super small file sizes, and can be easily edited and modified.
So why use any other image format, ever? Well, except for the times when you need an actual image of course. But let's leave the "why" with this brief introduction, and jump straight into the "how".
How to use an SVG in a React app, slightly depends on the rest of your website architecture, but most importantly whether the SVG should respond to any kind of interactions. Let's start with the simplest example:
Static SVGs
The easiest way of implementing a static SVG in a React app is like this:
const App = () => (
<img src="/path/image.svg" alt="" />
)
After all, the img
tag simply refers to an image, either through an absolute
or a relative path. However, SVGs are often pretty small, and in these cases we
are usually better off inlining the images
as Data URLs, limiting the amount of
requests a browser has to make to the server in order to render the webpage.
To transform an image into a Data URL, we need an appropriate loader in our
bundler. I'm going to be using loaders written for webpack in my examples, but
alternatives should be available for most other popular bundlers. For
transforming files into Data URLs, the most common webpack loader
is url-loader
, which can be
added in the following manner
Install as a devDependency
:
$ npm i --save-dev url-loader
Add to webpack.config.js
:
rules: [
{
test: /\.(png|jpg|gif|svg)$/i,
use: [
{
loader: 'url-loader',
options: {
limit: 10000
}
}
]
}
];
Import the file in your React app:
import image from 'path/image.svg'
const App = () => (
<img src={image} alt="" />
)
The output in the DOM will be something like this
<img src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbm..." alt="">
Now, url-loader
actually transforms the images into base64 URIs. Seeing as an
SVG is a human-readable xml string, using base64 encoding is not mandatory.
Instead, we can use the more explicitly named svg-url-loader
, which loads SVG
files as an UTF-8 encoded Data URL string. The benefits of this are according
to the README
:
- Resulting string is shorter (can be ~2 times shorter for 2K-sized icons);
- Resulting string will be compressed better when using gzip compression;
- Browser parses utf-8 encoded string faster than its base64 equivalent.
In the example above, I defined a limit
in the options object of the loader.
This tells webpack to only encode images that does not exceeds 10 kB in size.
Both url-loader
and svg-url-loader
uses file-loader
as a fallback, for files that exceeds this limit. What file-loader
does, is
basically to create a
copy of the image file in your output directory. So if the SVG in the example
above had exceeded 10 kB, the output in the DOM would look more like this:
<img src="ebbc8779488b4073081931bd519478e8.svg" alt="" />
This can be a bit cryptic though, so in order to get a more sensible naming of
files in your output directory, you can add name: '[path][name].[ext]'
to the
loader options, like this:
rules: [
{
test: /\.svg$/,
use: [
{
loader: 'svg-url-loader',
options: {
limit: 10000,
name: '[path][name].[ext]'
}
}
]
}
];
Notice how I used svg-url-loader
in this example, and also limited the test
regex to only include .svg
.
These loaders also work if you want to use SVGs as background-image
or
content
in CSS. Fun fact though: Beware of using background-size
if you
intend to change background-image
on hover or focus. Safari doesn't like this
very much.
Static, but animated, SVGs
Say you have an animated spinner, or some other kind of fancy loader, created in SVG. You might think that you would need to inline this SVG, in order to animate its properties with CSS. Good news: You don't!
It is true that an SVG loaded as an image, as described above, can't be modified
from "the outside". However, as long as the animation is either applied as CSS
within the SVG itself (with a <style>
element), or with an SVG <animate>
element, this animation will run however the SVG is implemented on the page.
Interactive SVGs
Now, to the really fun part! If you want to have full control of your SVGs from
within your app, and how they respond to different interactions, inlining them
is the way to go. In theory, you don't necessarily need a loader for this, you
could just copy the SVG code directly into a component. There are a few reasons
why this might not be the best way to go though. Say you were to change some
part of the SVG through a visual editor, such as Sketch or Adobe Illustrator.
Wouldn't it be decidedly easier to just switch the SVG file, and not have to
copy paste over the SVG code within the respective component? In order to avoid
this, you can use react-svg-loader
:
rules: [
{
test: /\.inline.svg$/,
loader: 'react-svg-loader'
}
];
import Image from 'path/image.svg';
const App = () => <Image width={50} height={50}/>;
Notice how I prefixed the test regex with inline
. This is because you likely
don't want all SVGs in your app to be inline. Say you were to only have this
loader, and then used an SVG as a background-image
. Your resulting CSS would
look something like this:
background-image: url(function Image(props){return React.createElement(
'svg',
props,
React.createElement(...)
)});
So usually, we want to use both react-svg-loader
and svg-url-loader
. To
avoid the latter trying to parse an SVG file supposed to be inline, we need to
exclude them from the loader. In this case, where inline SVGs include inline
before the file extension, the full set of loaders for SVGs would look like:
{
test: /^(?!.*\.inline\.svg$).*\.svg$/,
loader: 'svg-url-loader',
options: {
limit: 10000,
name: '[path][name].[ext]',
},
},
{
test: /\.inline.svg$/,
loader: 'react-svg-loader',
}
Of course, you could also choose to put the files in different folders, and
rather use the exclude
and include
options on each loader. Furthermore,
there are a couple of other loaders that does more or less the same as the ones
I've mentioned here. But hopefully this has been useful anyway.
Happy SVGing!