Cover image for Implementing MacOS Liquid Glass Effect in Hexo Butterfly Theme

Implementing MacOS Liquid Glass Effect in Hexo Butterfly Theme

Words 1.6k
Views
Visitors

Timeline

Timeline

2025-12-27

init

This article introduces the specific method for implementing the MacOS liquid glass effect in the Hexo Butterfly theme. The article discusses in detail the implementation approach based on multi-layer overlay (including container shell, liquid blur layer, tint mask layer, highlight layer, and content layer) and SVG filters, and summarizes the hover interaction dynamic effects and the modification steps for practical application in the theme.

Saw it on Codepen, made some slight modifications, you can see the effect on the aside card or nav of this site.

Implementation Approach

The main idea is to use multi-layer overlay, totaling four layers

1
2
3
4
5
6
7
8
<div class="liquidGlass-wrapper">
<div class="liquidGlass-effect"></div> <!-- 模糊 + 扭曲 -->
<div class="liquidGlass-tint"></div> <!-- 半透明白色蒙层 -->
<div class="liquidGlass-shine"></div> <!-- 内发光高光 -->
<div class="liquidGlass-box"> <!-- 实际内容容器 -->
<!-- 内容 -->
</div>
</div>

liquidGlass-wrapper

Container Shell

1
2
3
4
5
6
7
8
.liquidGlass-wrapper {
box-shadow: 0 6px 6px rgba(0, 0, 0, 0.2), 0 0 20px rgba(0, 0, 0, 0.1) !important;
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 2.2) !important;
background: none !important;
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
border-style: none !important;
}
  • Function: The outermost container, responsible for overall shadow and animation transitions.
  • Key Points
    • background: noneandbackdrop-filter: noneis to prevent it from blurring itself, leaving the blur to the interior.liquidGlass-effect
    • Shadows simulate the three-dimensional feel of glass.
    • transitionMakes the changes in border-radius and padding smoother on hover.

liquidGlass-effect

Core ‘Liquid’ Blur Layer

1
2
3
4
5
6
7
8
9
10
.liquidGlass-effect {
position: absolute;
z-index: 0;
inset: 0;

backdrop-filter: blur(3px);
filter: url(#glass-distortion);
overflow: hidden;
isolation: isolate;
}
  • backdrop-filter: blur(3px)
    Blurs the background content (such as other parts of the webpage) → This is the basis of ‘frosted glass’.
  • filter: url(#glass-distortion)This is the key to the ‘liquid’ feel! It references an SVG filter (#glass-distortion), usually defined in the page’s<svg>, used to applydistortion, ripples, liquid flowand other effects to the image.
  • isolation: isolate Ensures that this layer creates a new stacking context to prevent the filter from affecting other elements.

glass-distortion

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<svg style="display: none">
<filter
id="glass-distortion"
x="0%"
y="0%"
width="100%"
height="100%"
filterUnits="objectBoundingBox"
>
<feTurbulence
type="fractalNoise"
baseFrequency="0.01 0.01"
numOctaves="1"
seed="5"
result="turbulence"
/>
<!-- Seeds: 可替换为其他值如 14, 17 等 -->

<feComponentTransfer in="turbulence" result="mapped">
<feFuncR type="gamma" amplitude="1" exponent="10" offset="0.5" />
<feFuncG type="gamma" amplitude="0" exponent="1" offset="0" />
<feFuncB type="gamma" amplitude="0" exponent="1" offset="0.5" />
</feComponentTransfer>

<feGaussianBlur in="turbulence" stdDeviation="3" result="softMap" />

<feSpecularLighting
in="softMap"
surfaceScale="5"
specularConstant="1"
specularExponent="100"
lighting-color="white"
result="specLight"
>
<fePointLight x="-200" y="-200" z="300" />
</feSpecularLighting>

<feComposite
in="specLight"
operator="arithmetic"
k1="0"
k2="1"
k3="1"
k4="0"
result="litImage"
/>

<feDisplacementMap
in="SourceGraphic"
in2="softMap"
scale="150"
xChannelSelector="R"
yChannelSelector="G"
/>
</filter>
</svg>

liquidGlass-tint

Tint Mask Layer

1
2
3
4
5
6
.liquidGlass-tint {
z-index: 1;
position: absolute;
inset: 0;
background: rgba(255, 255, 255, 0.25);
}
  • Overlay a layer ofsemi-transparent white(can also be changed to light blue, light gray, etc.).
  • Simulates the ‘reflective base color’ of real glass, making text/content easier to read.
  • z-index: 1Ensure it is above the effect and below the shine.

liquidGlass-shine

Highlight layer

1
2
3
4
5
6
7
8
9
10
.liquidGlass-shine {
position: absolute;
inset: 0;
z-index: 2;

overflow: hidden;

box-shadow: inset 2px 2px 1px 0 rgba(255, 255, 255, 0.5),
inset -1px -1px 1px 1px rgba(255, 255, 255, 0.5);
}
  • Use Inner shadow (inset box-shadow) Simulate the glass edge’sHighlights and micro-reflections
  • Top right bright (+2px white light), bottom left slightly dark (-1px white light) → Create a sense of light direction.
  • z-index: 2Place on the top layer (but below the content).

liquidGlass-box

Content layer

1
2
3
4
5
6
7

.liquidGlass-box {
z-index: 3;
position: relative;
box-shadow: none !important;
}

  • The actual place to put content (such as avatars, text).
  • z-index: 3Ensure the content is above all glass effect layers and clearly visible.
  • position: relativeMake it out of the normal flow, but still inside the wrapper.

Interaction effects (Hover dynamics)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* OTHER STYLES */

.liquidGlass-wrapper,
.liquidGlass-wrapper > div:not(.liquidGlass-box) {
padding: 0.4rem !important;
border-radius: var(--glass-border-radius) !important;
}

.liquidGlass-wrapper:hover {
padding: 0.6rem !important;
border-radius: calc(var(--glass-border-radius) + 1em) !important;
}

.liquidGlass-wrapper > div:not(.liquidGlass-box):hover {
border-radius: calc(var(--glass-border-radius) + 1em) !important;
}
  • On hover
    • Border radius becomes larger (+1em) → Simulate “liquid expansion” or “soft deformation”;
    • Padding increases → Overall “breathing” feel;
    • All non-content layers synchronize border radius changes to maintain visual consistency.

Applied to the butterfly example

Below, takingcard_authoras an example, which is a card displaying author information.

card_author.pug

Modifythemes/butterfly/layout/includes/widget/card_author.pug

1
2
3
4
5
6
7
8
9
10
11
12
13
if theme.aside.card_author.enable
.card-widget.card-info.text-center.liquidGlass-wrapper
.liquidGlass-effect
.liquidGlass-tint
.liquidGlass-shine
.liquidGlass-box
div.card-info-avatar
.avatar-img
img(
src=url_for(theme.avatar.img)
onerror="this.onerror=null;this.src='" + url_for(theme.error_img.flink) + "'"
alt="avatar"
)

liquid_glass.css

Create a custom CSS filethemes\butterfly\source\css\liquid_glass.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/* LIQUID GLASS STYLES */
:root {
--glass-border-radius: 1.2rem; /* Default border radius */
}

.liquidGlass-wrapper {
box-shadow: 0 6px 6px rgba(0, 0, 0, 0.2), 0 0 20px rgba(0, 0, 0, 0.1) !important;
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 2.2) !important;
background: none !important;
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
border-style: none !important;
}

.liquidGlass-effect {
position: absolute;
z-index: 0;
inset: 0;

backdrop-filter: blur(3px);
filter: url(#glass-distortion);
overflow: hidden;
isolation: isolate;
}

.liquidGlass-tint {
z-index: 1;
position: absolute;
inset: 0;
background: rgba(255, 255, 255, 0.25);
}

.liquidGlass-shine {
position: absolute;
inset: 0;
z-index: 2;

overflow: hidden;

box-shadow: inset 2px 2px 1px 0 rgba(255, 255, 255, 0.5),
inset -1px -1px 1px 1px rgba(255, 255, 255, 0.5);
}

.liquidGlass-box {
z-index: 3;
position: relative;
box-shadow: none !important;
}

/* OTHER STYLES */

.liquidGlass-wrapper,
.liquidGlass-wrapper > div:not(.liquidGlass-box) {
padding: 0.4rem !important;
border-radius: var(--glass-border-radius) !important;
}

.liquidGlass-wrapper:hover {
padding: 0.6rem !important;
border-radius: calc(var(--glass-border-radius) + 1em) !important;
}

.liquidGlass-wrapper > div:not(.liquidGlass-box):hover {
border-radius: calc(var(--glass-border-radius) + 1em) !important;
}

inject

Theme’s_config.yamlImport in the inject of

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
inject:
head:
- <link rel="stylesheet" href="/css/liquid_glass.css">
bottom:
- |
<svg style="display: none">
<filter
id="glass-distortion"
x="0%"
y="0%"
width="100%"
height="100%"
filterUnits="objectBoundingBox"
>
<feTurbulence
type="fractalNoise"
baseFrequency="0.01 0.01"
numOctaves="1"
seed="5"
result="turbulence"
/>
<!-- Seeds: 可替换为其他值如 14, 17 等 -->

<feComponentTransfer in="turbulence" result="mapped">
<feFuncR type="gamma" amplitude="1" exponent="10" offset="0.5" />
<feFuncG type="gamma" amplitude="0" exponent="1" offset="0" />
<feFuncB type="gamma" amplitude="0" exponent="1" offset="0.5" />
</feComponentTransfer>

<feGaussianBlur in="turbulence" stdDeviation="3" result="softMap" />

<feSpecularLighting
in="softMap"
surfaceScale="5"
specularConstant="1"
specularExponent="100"
lighting-color="white"
result="specLight"
>
<fePointLight x="-200" y="-200" z="300" />
</feSpecularLighting>

<feComposite
in="specLight"
operator="arithmetic"
k1="0"
k2="1"
k3="1"
k4="0"
result="litImage"
/>

<feDisplacementMap
in="SourceGraphic"
in2="softMap"
scale="150"
xChannelSelector="R"
yChannelSelector="G"
/>
</filter>
</svg>

Reference