SVG has emerged as a dominant image format, offering unparalleled flexibility and scalability for today’s web development landscape. From its humble beginnings to its widespread adoption, SVG has undergone significant evolution and now plays a pivotal role in shaping the modern web.
What are SVG files used for?
SVG, or Scalable Vector Graphics, was first introduced in 1999 as a web standard by the World Wide Web Consortium (W3C). Since then, it has experienced significant advancements and widespread adoption within the web development community.
Unlike raster images, which are composed of pixels and can lose quality when resized, SVG images are composed of scalable vector shapes defined by mathematical equations. This vector-based format ensures that SVG images retain their clarity and sharpness regardless of the display size, making them ideal for responsive and high-resolution interfaces.
In a nutshell, SVG images are ideal for:
- Icons
- Logos
- Illustrations
Tools and Techniques
Before diving into the code example, it’s important to familiarize yourself with the available tools. Not all tools are created equal, and if you’re committed to standardizing your SVG files for dynamic manipulation, it’s crucial to be aware of the right tools for the job.
Vector Magic: Converting PNG to SVG
When it comes to converting PNG images to SVG, Vector Magic stands out as a reliable tool with a long-standing reputation. While Vector Magic is not free, its automatic vectorization capabilities have made it a go-to choice for many designers and developers. It’s worth noting that it also supports other raster image formats like JPG, BMP, and GIF. With a few simple steps, you can convert bitmap images to high-quality SVG vectors.
However, it’s important to consider certain factors when converting images to SVG. Highly detailed images may result in a larger file size in the SVG format compared to the raster image. Due to the nature of vector graphics, some complex images may not translate as effectively, potentially impacting the overall quality of the resulting SVG.
Inkscape: A Free SVG Editing Software
Inkscape is a versatile and powerful open-source vector graphics editor that excels at editing SVG images. It offers a comprehensive set of tools for creating and modifying SVGs, including drawing, text manipulation, and object transformations. Inkscape also allows for precise editing at the individual node level, making it an excellent choice when precision edits are necessary. While Inkscape provides a rich feature set, it’s important to note that working with complex operations or large files may require additional system resources. Additionally, keep in mind that Inkscape has certain limitations in terms of performance and file handling.
Figma: Smart Resizing Capabilities
Figma, primarily known as a collaborative design tool, also offers robust capabilities for working with SVG images. While not specifically marketed as an SVG editor, Figma provides an intuitive interface and powerful features that make it a viable option for manipulating SVGs. It’s worth mentioning that Figma’s SVG editing capabilities are not as extensive as dedicated tools like Inkscape. However, Figma excels in its ability to resize SVG images without having to rely heavily on the transform
attribute. This makes Figma a valuable complement to Inkscape, particularly if you are looking to keep the size consistent across images of the same type (e.g., icons).
Free SVG Assets Online
In addition to creating and editing SVG images, it’s often beneficial to leverage existing SVG assets to enhance your designs. Fortunately, numerous sources offer free SVG assets online. Websites such as SVG Repo, Freepik, and Flaticon provide extensive collections of high-quality SVG icons, illustrations, and other graphical resources that can serve as a starting point or inspiration for your SVG creations.
Optimization Tools for SVG Images
In addition to the techniques we’ve discussed so far, there are optimization tools available that can further enhance SVG images. These tools, such as SVGO and ImageOptim, offer valuable features to reduce file size and clean up SVG markup, making it easier to standardize and optimize the overall performance of SVG assets.
- SVGO (SVG Optimizer) is a popular tool known for its ability to optimize SVG files. It analyzes the markup and applies various techniques to reduce unnecessary code and optimize the structure of the SVG. By eliminating redundant elements, attributes, and whitespace, SVGO helps reduce file size without compromising the visual quality of the image. It’s like a magic wand that streamlines your SVGs and makes them lighter, ready to be delivered to your users at lightning speed.
- ImageOptim, on the other hand, is a versatile optimization tool that supports a wide range of image formats, including SVG. It employs advanced compression algorithms to squeeze out every unnecessary byte from your SVG files, resulting in smaller file sizes. By reducing the file size, you not only enhance the loading speed of your web pages but also save precious bandwidth, making your website more efficient and eco-friendly. Plus, ImageOptim has a friendly interface that makes the optimization process enjoyable and effortless.
- Vector Magic offers a lesser-known technique to further reduce the size of SVG images. SVG files can sometimes turn out larger than expected, even after applying other optimization methods. One clever “hack” involves converting the SVG image back to a PNG file and then re-processing it through Vector Magic with different settings. By selectively reducing the level of detail during the conversion, you can achieve remarkable size reductions. I’ve witnessed SVG images being compressed by up to 60% using this advanced technique.
Making SVG images Dynamic
Next.js comes with an image component that supports both traditional raster images like PNG and SVG files. It uses the
HTML tags and loads the images from the public
directory, which works well for static images like illustrations. However, if you want to make SVG images dynamic, you have a few options to consider.
What are the options to make SVG images dynamic?
One popular approach is to create SVG components using JSX. Here’s an example:
const CheckMarkComponent: React.FC<{
color?: string;
strokeWidth?: string;
}> = ({ color, strokeWidth }) => {
return (
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<g
fill="none"
stroke={color ?? "black"}
stroke-linecap="round"
stroke-linejoin="round"
stroke-width={strokeWidth ?? "1px"}
transform="matrix(1,0,0,1,0,0)"
>
<path d="M23.25.749,8.158,22.308a2.2,2.2,0,0,1-3.569.059L.75,17.249" />
g>
svg>
);
};
The benefit of this approach is that you have more flexibility to customize the SVG since it becomes a React component. However, there’s a drawback: it’s no longer a standalone SVG image. This means you won’t have the convenience of basic tools like SVG previews or thumbnail browsing in directories. It also makes editing the image a bit more cumbersome, as you’ll need to copy and paste between different tools.
Considering these limitations, I suggest using the SVGR Webpack loader, which allows you to import SVG images directly into your Node.js application.
⚠ It’s important to note that when using this solution, the SVG images will be imported directly into your JavaScript bundles. As a result, the overall page size will increase compared to the native Next.js version that loads images asynchronously. For this reason, I don’t recommend using this solution for large illustrations unless they require dynamic functionality. However, it’s an excellent solution for icons since they become part of the page, eliminating any flickering during image loading.
Getting started
As we delve into this discussion, it’s important to know that we’ve conveniently made the solutions discussed in this article accessible via our GitHub repository. This will allow you to engage more practically with the topics we explore.
Now, in order to transform SVG images within your Next.js application, our first step involves installing the SVGR Webpack loader. To do this, open your terminal and execute the following command:
npm i @svgr/webpack --save-dev
Next, we need to configure Next.js (next.config.js) to recognize the SVGR Webpack loader when importing SVG files. Add the following code to your next.config.js
file:
/** @type {import('next').NextConfig} */
const nextConfig = {};
nextConfig.webpack = (config, context) => {
config.module.rules.push({
test: /.svg$/,
use: "@svgr/webpack",
});
return config;
};
module.exports = nextConfig;
Now, to ensure TypeScript recognizes the imported SVG files properly, we’ll create a global.d.ts
file at the root of your project (if you don’t already have another type file you’re using). Add the following declaration to the global.d.ts
file:
// Allow TypeScript to import SVG files using Webpack's `svgr` loader.
declare module "*.svg" {
import React from "react";
const content: React.FC<React.SVGProps<SVGSVGElement>>;
export default content;
}
In order to eliminate the necessity of adding boilerplate code across our examples, we will be utilizing the following two files located under the /app
directory of our Next.js project:
layout.tsx
import "./global.css";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html>
<body
style={{
position: "absolute",
padding: 0,
margin: 0,
height: "100%",
width: "100%",
}}
>
<div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
minHeight: "100%",
height: "auto",
}}
>
<div style={{ margin: "10%" }}>{children}div>
div>
body>
html>
);
}
global.css
svg {
width: 100%;
}
Now that we have the basic setup ready, let’s test it out by creating a new page under the /app
directory. Unlike the Next.js
component, we don’t need to place the SVG file in the /public
directory. Instead, we can import the SVG files directly into the pages.
To begin, let’s create a new directory named check-mark
and add a new SVG image called CheckMark.svg
with the following markup:
Next, we can create a new file named page.tsx
to display the SVG file:
import CheckMark from "./CheckMark.svg";
export default function CheckMarkPage() {
return <CheckMark />;
}
Now, if we start Next.js and load the page, we should see a black check mark in the center.
Adding dynamism
If you’ve been paying attention to the SVG images, you might have noticed a special value used for the stroke
attribute: currentColor
. This value is not exclusive to SVG images but is a CSS value that can be utilized in various contexts, including SVG. It refers to the value of the color property of the element that is currently being styled.
In the case of SVG, the stroke
property determines the color of the outline or stroke of a shape or path. When you set stroke="currentColor"
, you’re instructing the SVG element to use the same color as its parent element.
This means that if you set the color
of the parent
red
, the
element, the SVG image, will also become red. This is particularly handy when using SVG images as icons embedded within text. In most designs, icons share the same color as the text. However, if you ever need to use a different color, you can specify it directly on the SVG component like this:
<CheckMark color={"green"} />
This feature is incredibly powerful because it allows you to dynamically select the exact color you need from the same SVG file without duplicating it. You can also apply this technique to almost every supported property.
Another common use case is modifying the stroke width. By default, you can have a small stroke width that works well for icons, but you might need to adjust it if you want to reuse the same image in a larger format.
Let’s try modifying the stroke width of the image:
<CheckMark strokeWidth={"5px"} color={"blue"} />
As expected, this changes the stroke width. However, now we encounter a new problem—the SVG image is larger than its viewbox, and it appears truncated.
Fixing truncated images
Unfortunately, there is no concept of margin for SVG viewboxes. However, there is a way to fix this issue by resizing the viewbox and using negative values to center the image. Luckily, we have already created a component to handle this. You can create the helper function under the /src directory and name it SvgMargin.ts
:
import React from "react";
/**
* Type guard to check if a React node is a functional component.
*
* @param node - A React node to check.
*
* @returns True if it's a functional component, otherwise false.
*/
const isFunctionalComponent = (
node: React.ReactNode
): node is React.FunctionComponentElement<React.SVGProps<SVGSVGElement>> => {
return (
node !== null &&
typeof node === "object" &&
"type" in node &&
typeof node.type === "function"
);
};
/**
* Get the name of a component.
*
* @param component - A component.
*
* @returns The component name.
*/
const getComponentName = (component: React.ReactElement) =>
typeof component.type === "string"
? component.type
: (component?.type as React.FunctionComponent)?.displayName ||
component?.type?.name ||
"Unknown";
/**
* Component to add margin around an SVG image.
*/
export const SvgMargin: React.FC<{
children: React.ReactElement<React.SVGProps<SVGSVGElement>>;
/** The size of the margin to apply to the SVG image (e.g., 5 will be 5% of the image height/width). */
size: number;
}> = ({ children, size: marginRatio }) => {
if (!isFunctionalComponent(children)) {
return children;
}
const SvgComponent = children.type({});
if (!React.isValidElement<React.SVGProps<SVGSVGElement>>(SvgComponent)) {
return children;
}
const viewBox =
children?.props?.viewBox ?? SvgComponent?.props?.viewBox ?? "";
const [x, y, width, height] = viewBox
.split(" ")
.map((value) => parseFloat(value));
if ([x, y, width, height].some((val) => val == null || isNaN(val))) {
console.error(
`missing viewBox property for svg ${getComponentName(SvgComponent)}`
);
return children;
}
const margin = marginRatio / 100;
// Calculate new x and width values.
const widthMargin = width * margin;
const newX = x - widthMargin;
const newWidth = width + 2 * widthMargin;
// Calculate new y and height values.
const heightMargin = height * margin;
const newY = y - heightMargin;
const newHeight = height + 2 * heightMargin;
return React.cloneElement(
SvgComponent,
{
...children.props,
viewBox: `${newX} ${newY} ${newWidth} ${newHeight}`,
},
SvgComponent.props.children
);
};
And the output:
Controlling shapes and paths independently
The current solution we just demonstrated works for the most common use case. It allows you to change properties for the entire SVG image. However, it has a limitation: you can only change each property to a single value. This means that if you change the color
property, it will apply to all shapes and paths that use the currentColor
. So, how can we achieve more control?
There are three primary solutions to gaining more control:
- Use inline (in the SVG file)
- Use Next.js’ built-in CSS modules
- Use another helper function
1) Using inline style
Using inline styles is a straightforward option that allows you to leverage the SVG’s inline style capabilities, similar to HTML CSS. The main difference is that the SVGR loader, by default, renames both the class names and id
properties to avoid collisions with HTML styles. However, this renaming process occurs behind the scenes, and you’ll only see the end result when inspecting the SVG within your browser. To understand how this works, let’s create a new directory called circles-inline-style
and add a new SVG file called CirclesInlineStyle.svg
:
In this example, we’ve added id
properties to all circles, allowing us to target them individually within the