跪拜 Guibai
← Back to the summary

Recoloring External SVGs in Mini-Programs with a Drop-Shadow Silhouette

Scenario: When optimizing the main/sub-package size of a mini-program, replace the project's own iconfont icons with images (SVG/PNG). Difficulty: SVG loaded via <image> cannot be recolored with CSS color. This article provides a "silhouette stand-in" solution using filter: drop-shadow, allowing replaced icons to still be freely recolored via CSS. Note: Paths and domains in the text are placeholder examples; replace them with your project's actual values.

1. Background and Problem

Why Replace Iconfont

Font icons (iconfont) bundle an entire .ttf/.woff font file into the package. There are three reasons why this wastes space (often overlooked):

  1. The entire glyph set is bundled together; using a few means carrying hundreds. An iconfont typically consists of a single font file, but it contains all glyphs of that icon set. Even if the set has 200 icons and you only use 5, the 195 unused glyphs are still in the file.
  2. Font files are almost impossible to tree-shake. In regular JS, unused code can be removed by build tools, but a font is a monolithic binary; build tools cannot delete unused glyphs inside it, so it cannot be automatically slimmed down like code.
  3. It's often worse in mini-programs—fonts are frequently base64-inlined into CSS. Mini-programs have restrictions on loading external font files, so many projects directly convert the entire font file into a base64 string embedded in CSS. Base64 is about 33% larger than the original binary; if this CSS is referenced by multiple pages/sub-packages, it may also be duplicated, further amplifying the size.

Conclusion: The waste of iconfont = "full glyph set cannot be pruned + possible base64 inlining/duplication". You carry the whole set even if you use only a few. When only a few icons are used, or when optimizing package size, replacing these icons with on-demand images loaded from CDN/OSS (SVG/PNG) is often more economical—icon resources are moved out of the package, leaving only a URL string in the package, fetched from the network at runtime as needed.

The Pitfall After Switching to Images: Loss of Color Control

A common misconception is "SVG can be recolored with CSS, PNG cannot." This statement is only true for "inline SVG":

Usage Can CSS color recolor the SVG?
Inline SVG: <svg><path fill="currentColor"/></svg> written directly into the DOM ✅ Yes, color is passed in via currentColor
<image src="x.svg"> : Loading SVG as an external image resource ❌ No, it's a "replaced element" like PNG; CSS cannot penetrate inside

In mini-programs, <image> is a native component that decodes SVG (or PNG) into a bitmap for rendering, with no CSS cascade entering the image's interior. Therefore:

<!-- Expecting white, but color is ineffective; the final display is the baked-in color of the SVG file (e.g., black) -->
<image class="icon" src="/static/icon-arrow.svg"></image>
.icon { color: #fff; }  /* Completely ineffective for the SVG inside <image> */

If the SVG file itself is black but the design mockup requires white, color:#fff cannot save it. At this point, you either modify the SVG file's own color (inflexible, requiring a separate file for each color) or switch to a different technical approach—which is the drop-shadow silhouette method below.

2. Core Idea: Using the "Projection" as a Stand-in

In one sentence: Since you cannot change the color of the image's real body, use its "projection" as a stand-in placed where it should be, and then color the projection.

The principle relies on a characteristic of CSS filter: drop-shadow(): it generates a solid-colored silhouette based on the image's opaque shape (alpha channel), and the silhouette's color can be controlled by CSS.

A three-step coordination (assuming the icon size is H):

  1. Hide the real body: Set overflow: hidden on the outer container, and use transform: translateY(-H) to push the original image upwards out of the container, where it is clipped and invisible.
  2. Fill the gap with the projection: Add filter: drop-shadow(0 H 0 currentColor) to the image, offsetting the projection downwards by H. The real body moves up H, the projection moves down H; the up and down offsets cancel each other out, and the projection lands precisely back in the visible area.
  3. Color the projection: The projection color uses currentColor, which is the CSS color. Thus, "set color → icon changes color" is re-established.

Ultimately, the icon you see is actually that solid-colored silhouette that can be dyed by CSS.

3. Code Implementation

Basic Structure

<view class="svg-icon">
  <image class="icon-svg" src="/static/icon-arrow.svg" mode="aspectFit"></image>
</view>
/* Outer: visible window + determines color */
.svg-icon {
  display: inline-flex;
  width: 32rpx;
  height: 32rpx;
  overflow: hidden;      /* Key: clips the pushed-out original image */
  color: #1A4D85;        /* Target color, which currentColor will adopt */
}

/* Inner image: move up + projection fill */
.icon-svg {
  width: 32rpx;
  height: 32rpx;
  transform: translateY(-32rpx);              /* Push out of window */
  filter: drop-shadow(0 32rpx 0 currentColor); /* Projection offset down, lands back in window, adopts color */
}

Key points:

Variant 1: Icons with Status Colors (Reusing Color Classes)

When different states require different colors, attach the color class to the outer layer; currentColor inherits automatically:

<view class="svg-icon done"><image class="icon-svg" src="/static/icon-check.svg"></image></view>
<view class="svg-icon warn"><image class="icon-svg" src="/static/icon-warn.svg"></image></view>
.svg-icon .icon-svg { /* Same as above: width/height + translateY + drop-shadow(... currentColor) */ }
.done { color: #26BF76; }
.warn { color: #E6A23C; }

Variant 2: Icons Needing Rotation/Animation (Avoiding Transform Conflicts)

If the icon itself also needs to rotate (e.g., a dropdown arrow flipping 180° when expanded), note that the translateY required by drop-shadow also occupies transform, and both cannot be placed on the same element. The solution: place rotation on the outer layer, and translateY on the inner image, each using its own transform:

<view class="svg-icon" :class="{ active: open }">
  <image class="icon-svg" src="/static/icon-arrow-down.svg"></image>
</view>
.svg-icon {
  display: inline-flex; width: 32rpx; height: 32rpx; overflow: hidden;
  color: #fff;
  transition: transform 0.2s ease;
}
.svg-icon.active { transform: rotateZ(180deg); } /* Rotation on outer layer */
.icon-svg {
  width: 32rpx; height: 32rpx;
  transform: translateY(-32rpx);                 /* translateY on inner layer */
  filter: drop-shadow(0 32rpx 0 currentColor);
}

When the outer layer rotates, the inner layer's projection rotates with it; the arrow flips normally without interference.

4. A Side Effect You Must Understand: DOM Position ≠ Visual Position

This is an inherent characteristic of this solution, which can be confusing during debugging:

Conclusion: The element is above, the shadow is below; what you see is the shadow.

Practical implications:

5. Applicability Boundaries and Precautions

  1. Can only dye a single solid color: The projection is a silhouette that "fills the entire shape with one color"; multi-color icons will be crushed into a single color. Suitable for single-color icons (arrows, eyes, checkmarks, etc.), not for multi-color logos.
  2. Mini-program filter compatibility: filter: drop-shadow has limited support on some mini-program platforms/base library versions. Must verify on a real device with the target base library; passing in developer tools does not guarantee passing on a real device.
  3. Size/offset must be consistent: The Y offsets of translateY and drop-shadow must both equal the icon height; when changing sizes, the three places (width/height, translateY, drop-shadow) must be updated synchronously.
  4. Use with caution at very small sizes: When the icon is very small (e.g., 7px), the projection offset is also very small, which may cause sub-pixel blurring or slight misalignment on real devices; real-device testing is required.
  5. Prefer inline SVG when possible: If the environment allows direct inline <svg fill="currentColor"> (e.g., H5), that is a more direct way to control color, and this technique is unnecessary. This solution mainly addresses scenarios where "only <image> can be used to load external SVG" (typically mini-programs).

6. Decision Quick Reference

Iconfont Usage Situation Suggested Solution
Single-color icon + CSS color control needed + only <image> possible (mini-program) ✅ This article's drop-shadow silhouette method
Fixed icon color, no CSS color control needed Direct <image> with SVG/PNG, simplest
Multi-color icon Use colored SVG/PNG directly (abandon CSS color control)
Environment supports inline SVG (H5, etc.) Inline <svg> + fill="currentColor"

7. Principle Summary

<image> is responsible for providing the "shape", and CSS, through the projection, is responsible for providing the "color and position"—splitting the icon's shape and color into two layers for processing. This is the essence of bypassing the limitation that "SVG inside <image> cannot receive CSS color."