Timeline 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 apply distortion, ripples, liquid flow and 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" /> <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 .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 :root { --glass-border-radius : 1.2rem ; } .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 ; } .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