Cover image for Implementing MacOS liquid glass effect in Hexo butterfly theme

Implementing MacOS liquid glass effect in Hexo butterfly theme

Words 1.7k
Views
Visitors
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) !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 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: isolate Ensure 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 !important;}
  • 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 !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 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.

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) !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

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>

References

Loading comments…