Understanding the Non-Zero Winding Rule
When you define a complex shape using thed attribute in an SVG <path>, the rendering engine must decide which areas are "inside" the shape and which are "outside." By default, most browsers use the non-zero winding rule. This algorithm works by casting a ray from a point outward to infinity and counting how many times the path crosses that ray. If the crossings cancel each other out, the point is considered outside.
For a simple rectangle, this is straightforward. However, when you attempt to create a hole by adding a second loop inside the first, the direction of the drawing matters immensely. If both the outer loop and the inner loop are drawn in the same direction, the winding numbers do not subtract correctly. The result is that the entire area, including the intended hole, is filled. This is why users often find that their "hole" disappears and becomes just another filled section of the design.
To visualize this, imagine walking along the path. If you walk around the outer edge clockwise and then walk around the inner edge also clockwise, the computer sees two positive windings. It does not see a subtraction. To create a void, you need one positive winding and one negative winding. This is where the concept of path direction becomes critical for troubleshooting.
The Solution: Opposite Path Directions
The most reliable way to create a hole within a single<path> element is to reverse the direction of the subpath that defines the hole. In SVG path data, commands like M (move to), L (line to), and C (cubic bezier) define the geometry. The order of these commands determines the direction.
For example, consider a square with a circular hole in the center. You would start with the outer square. Draw it in a clockwise direction. Then, move to the top-left of the circle. Instead of continuing clockwise, draw the circle in a counter-clockwise direction. You can achieve this by reversing the sequence of points or by changing the sign of the coordinates if you are generating the path programmatically.
Here is a conceptual example of how the data should look:
<path d="
M 10,10
L 90,10
L 90,90
L 10,90
Z
M 50,30
A 20,20 0 1,0 50,70
A 20,20 0 1,0 50,30
"/>
In this snippet, the outer square is defined first. The inner circle uses arc commands (A). Notice the flags in the arc command. The third flag indicates sweep-flag. Setting it to 1 draws clockwise, while 0 draws counter-clockwise. By ensuring the inner loop has the opposite sweep flag or reversed coordinate sequence, you signal to the renderer that this area should be excluded from the fill.
If you are using a vector graphics editor like Adobe Illustrator or Inkscape, these tools usually handle this automatically when you use the "subtract" or "exclude" operations. They generate the path data with the correct opposing directions behind the scenes. If you are hand-coding, you must manually verify this directionality.
Alternative Approach: Using Two Separate Paths
Sometimes, managing direction within a single complex path can become error-prone, especially if the shape is highly irregular. A simpler alternative is to use two separate<path> elements. You can draw the main shape as usual. Then, draw the hole as a separate path that covers the same area but uses the fill-rule="evenodd" attribute on the container or individual paths.
The evenodd rule is more forgiving than the non-zero rule. It simply counts whether a ray crosses an odd or even number of path segments. If it crosses once, it's inside. If twice, it's outside. This means you don't strictly need to worry about clockwise vs. counter-clockwise directions for both loops. As long as both the outer and inner boundaries are closed, the evenodd rule will treat the area between them as empty space.
This approach is particularly useful when dealing with multiple holes. Each hole can be its own subpath or separate path, and the evenodd rule ensures they all behave consistently without requiring strict directional control. However, note that some older renderers or specific CSS filters might behave differently with evenodd, so testing across browsers is recommended.
Common Pitfalls in Path Data
Even with correct directions, several issues can prevent holes from appearing. First, check for unclosed paths. If your inner loop does not end with aZ command or return to its starting point, the rendering engine may not recognize it as a distinct boundary. Always ensure every subpath is closed.
Second, overlapping lines can cause visual artifacts. If your inner path intersects with the outer path incorrectly, it might create unintended fills. Use the fill-rule attribute explicitly to avoid ambiguity. For instance, adding fill-rule="nonzero" or fill-rule="evenodd" to your <path> tag makes your intent clear to the browser.
Third, if you are generating SVGs via JavaScript, ensure that floating-point precision errors do not cause the paths to miss their endpoints slightly. Even a tiny gap can break the winding calculation. Rounding your coordinates to two decimal places can often resolve these subtle glitches.
Finally, remember that strokes are handled separately from fills. A hole in the fill does not necessarily mean a hole in the stroke. If you want the border of the hole to disappear as well, you must ensure the path structure supports it, or use masks and clip-paths for more complex transparency effects.
Frequently Asked Questions
Why does my hole appear filled when I use the same direction for both loops?
Because the non-zero winding rule adds up the winding numbers, two loops in the same direction result in a net winding number of two, which is still considered "inside." Reversing the direction of the inner loop creates a net winding of zero, marking it as outside.Can I use masks instead of path directions to create holes?
Yes, using an SVG<mask> element is a powerful alternative. You define a white shape for the visible area and a black shape for the hole. This method ignores winding rules entirely and relies on alpha channel transparency, making it ideal for complex designs with many cutouts.
What is the difference between nonzero and evenodd fill rules?
Thenonzero rule considers direction, adding or subtracting windings based on clockwise or counter-clockwise movement. The evenodd rule ignores direction and simply counts the total number of crossings; an odd count means inside, and an even count means outside.
How do I debug path direction in Chrome DevTools?
You can inspect the SVG element in the Elements tab. Look at the computed styles forfill-rule. While you cannot directly see the winding direction visually, you can temporarily change the fill color to red and add a stroke to trace the path. Animate the path or use console logs to print the d attribute values to verify the sequence of commands.
Do I need to close the inner path with Z?
Yes, it is best practice to close the inner path with aZ command. While some browsers might infer closure, explicit closure ensures consistent behavior across all SVG renderers and prevents unexpected gaps or overlaps in the final image.