Timeline
Timeline
2025-12-27
init
This article introduces how to implement the MacOS liquid glass effect in the Hexo butterfly theme. It elaborates on the implementation approach, which uses a four-layer stacked structure to simulate the glass texture: the outermost container handles shadows and animation transitions; the core blur layer uses an SVG filter to achieve background distortion and a liquid flowing feel; the tint overlay adds semi-transparent white to enhance readability; and the highlight layer simulates edge reflections through inner shadows. It also explains how to place the content layer and the dynamic interaction effects on hover, including changes in border radius and padding adjustments. Finally, using the author info card as an example, it demonstrates the specific steps of modifying the pug file, creating custom CSS, and injecting it in the butterfly theme.
I saw it on CodePen and 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 stacking, with four layers in total.
12345678 | <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
12345678 | .liquidGlass-wrapper { box-shadow: 0 6px 6px rgba(0, 0, 0, 0.2), 0 0 20px rgba(0, 0, 0, 0.1) ; transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 2.2) ; background: none ; backdrop-filter: none ; -webkit-backdrop-filter: none ; border-style: none ;} |
- Function: The outermost container, responsible for overall shadow and animation transitions.
- Key points:
background: noneandbackdrop-filter: noneis to prevent itself from generating blur, handing the blur to the interior..liquidGlass-effect。- Shadows simulate the three-dimensional feel of glass.
transitionMake the border-radius and padding changes on hover smoother.
liquidGlass-effect
Core ‘liquid’ blur layer
12345678910 | .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)
Blur 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<svg>, used to apply to the imagedistortion, ripples, liquid flowand other effects.isolation: isolateEnsure this layer creates a new stacking context to prevent the filter from affecting other elements.
glass-distortion
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556 | <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 overlay
123456 | .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: 1Make sure it is above the effect and below the shine.
liquidGlass-shine
Shine layer
12345678910 | .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) Simulating the glass edgeHighlights and subtle reflections。
- Top-right bright (+2px white light), bottom-left slightly dark (-1px white light) → create a sense of light direction.
z-index: 2Place it on the topmost layer (but below the content).
liquidGlass-box
Content layer
1234567 | .liquidGlass-box { z-index: 3; position: relative; box-shadow: none ;} |
- The place where content is actually placed (e.g., avatar, text).
z-index: 3Ensure the content is above all glass effect layers and clearly visible.position: relativeTake it out of the normal flow, but still inside the wrapper.
Interaction effects (Hover dynamics)
12345678910111213141516 | /* OTHER STYLES */.liquidGlass-wrapper,.liquidGlass-wrapper > div:not(.liquidGlass-box) { padding: 0.4rem ; border-radius: var(--glass-border-radius) ;}.liquidGlass-wrapper:hover { padding: 0.6rem ; border-radius: calc(var(--glass-border-radius) + 1em) ;}.liquidGlass-wrapper > div:not(.liquidGlass-box):hover { border-radius: calc(var(--glass-border-radius) + 1em) ;} |
- On hover:
- Border radius increases (
+1em) → simulates ‘liquid expansion’ or ‘soft deformation’; - Padding increases → overall ‘breathing’ feel;
- All non-content layers change their border radius synchronously to maintain visual consistency.
- Border radius increases (
Applied to the butterfly example
Below, usingcard_authoras an example, i.e., the card that displays author information.
card_author.pug
Modifythemes/butterfly/layout/includes/widget/card_author.pug
12345678910111213 | 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
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465 | /* 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) ; transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 2.2) ; background: none ; backdrop-filter: none ; -webkit-backdrop-filter: none ; border-style: none ;}.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 ;}/* OTHER STYLES */.liquidGlass-wrapper,.liquidGlass-wrapper > div:not(.liquidGlass-box) { padding: 0.4rem ; border-radius: var(--glass-border-radius) ;}.liquidGlass-wrapper:hover { padding: 0.6rem ; border-radius: calc(var(--glass-border-radius) + 1em) ;}.liquidGlass-wrapper > div:not(.liquidGlass-box):hover { border-radius: calc(var(--glass-border-radius) + 1em) ;} |
inject
of the theme_config.ymlimport in the inject
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061 | 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> |