Components/GlassSurface
Universal Component

GlassSurface

Refractive glass container with SVG displacement, backdrop-filter, and solid fallback rendering paths.

This page was migrated by AI, please review carefully

Migration is complete, but please validate against source code and manual review.

GlassSurface

TxGlassSurface renders a bounded glass layer for translucent panels and optical distortion previews. It prefers the SVG displacement filter path, falls back to native backdrop-filter blur when SVG filters are unavailable, and finally uses a solid translucent surface when neither capability is supported.

Basic Usage

GlassSurface

Demo will load when visible.
<template>
  <TxGlassSurface :width="360" :height="160" :border-radius="20" :background-opacity="0">
    <div style="padding: 16px; font-weight: 700;">GlassSurface</div>
  </TxGlassSurface>
</template>

Parameter Tuning (Slider)

GlassSurface Parameter Tuning

Demo will load when visible.
<script setup lang="ts">
import { ref } from 'vue'

const width = ref(360)
const height = ref(160)
const borderRadius = ref(20)
const borderWidth = ref(0.08)
const brightness = ref(85)
const opacity = ref(0.9)
const blur = ref(10)
const displace = ref(0.6)
const backgroundOpacity = ref(0.08)
const saturation = ref(1.2)
const distortionScale = ref(-180)
const redOffset = ref(0)
const greenOffset = ref(10)
const blueOffset = ref(20)
const xChannel = ref<'R' | 'G' | 'B'>('R')
const yChannel = ref<'R' | 'G' | 'B'>('G')
const mixBlendMode = ref('difference')

const channels = ['R', 'G', 'B']
const blendModes = [
  'normal',
  'multiply',
  'screen',
  'overlay',
  'darken',
  'lighten',
  'color-dodge',
  'color-burn',
  'hard-light',
  'soft-light',
  'difference',
  'exclusion',
  'hue',
  'saturation',
  'color',
  'luminosity',
  'plus-darker',
  'plus-lighter',
]
</script>

<template>
  <div style="display: flex; flex-direction: column; gap: 16px;">
    <div
      style="
        position: relative;
        border-radius: 16px;
        padding: 20px;
        background:
          radial-gradient(circle at 10% 15%, rgba(255, 208, 164, 0.7), transparent 45%),
          radial-gradient(circle at 86% 20%, rgba(168, 214, 255, 0.7), transparent 46%),
          radial-gradient(circle at 20% 80%, rgba(198, 255, 228, 0.55), transparent 50%),
          linear-gradient(135deg, rgba(255, 255, 255, 0.85), rgba(236, 240, 248, 0.98));
        border: 1px solid rgba(0, 0, 0, 0.06);
        overflow: hidden;
      "
    >
      <div
        style="
          position: relative;
          height: 280px;
          border-radius: 14px;
          overflow: hidden;
          background: rgba(255, 255, 255, 0.5);
        "
      >
        <div
          style="
            height: 100%;
            overflow-y: auto;
            padding: 20px 22px 30px;
            display: flex;
            flex-direction: column;
            gap: 16px;
            font-family: 'Playfair Display', 'Times New Roman', serif;
            color: rgba(0, 0, 0, 0.72);
          "
        >
          <div style="display: flex; align-items: center; gap: 10px;">
            <div
              style="
                padding: 4px 10px;
                border-radius: 999px;
                font-size: 12px;
                letter-spacing: 0.08em;
                text-transform: uppercase;
                background: rgba(255, 255, 255, 0.8);
              "
            >
              Glass Journal
            </div>
            <div style="font-size: 12px; opacity: 0.6;">
              scroll to preview depth
            </div>
          </div>
          <div style="font-size: 18px; font-weight: 600;">
            Light, texture, and a quiet sense of depth
          </div>
          <div style="font-size: 13px; line-height: 1.6; opacity: 0.78;">
            A glass surface should feel breathable. Subtle blur, soft light dispersion, and layered
            gradients turn a flat panel into something you want to hover over.
          </div>
          <div style="display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px;">
            <div
              style="
                height: 120px;
                border-radius: 14px;
                background:
                  linear-gradient(135deg, rgba(255, 255, 255, 0.85), rgba(215, 230, 255, 0.9)),
                  radial-gradient(circle at 20% 20%, rgba(255, 208, 164, 0.7), transparent 55%);
                border: 1px solid rgba(0, 0, 0, 0.08);
              "
            />
            <div
              style="
                height: 120px;
                border-radius: 14px;
                background:
                  linear-gradient(135deg, rgba(255, 255, 255, 0.9), rgba(214, 255, 236, 0.9)),
                  radial-gradient(circle at 80% 20%, rgba(168, 214, 255, 0.7), transparent 55%);
                border: 1px solid rgba(0, 0, 0, 0.08);
              "
            />
          </div>
          <div
            style="
              display: grid;
              grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
              gap: 10px;
              font-family: 'IBM Plex Mono', 'Courier New', monospace;
              font-size: 12px;
            "
          >
            <div style="padding: 10px 12px; border-radius: 10px; background: rgba(255, 255, 255, 0.8);">
              blur: keep it airy
            </div>
            <div style="padding: 10px 12px; border-radius: 10px; background: rgba(255, 255, 255, 0.8);">
              light: soft highlight
            </div>
            <div style="padding: 10px 12px; border-radius: 10px; background: rgba(255, 255, 255, 0.8);">
              tint: warm + cool
            </div>
          </div>
          <div style="height: 120px;" />
        </div>

        <div
          style="
            position: absolute;
            inset: 0;
            display: flex;
            align-items: center;
            justify-content: center;
            pointer-events: none;
          "
        >
          <TxGlassSurface
            :width="width"
            :height="height"
            :border-radius="borderRadius"
            :border-width="borderWidth"
            :brightness="brightness"
            :opacity="opacity"
            :blur="blur"
            :displace="displace"
            :background-opacity="backgroundOpacity"
            :saturation="saturation"
            :distortion-scale="distortionScale"
            :red-offset="redOffset"
            :green-offset="greenOffset"
            :blue-offset="blueOffset"
            :x-channel="xChannel"
            :y-channel="yChannel"
            :mix-blend-mode="mixBlendMode"
          >
            <div
              style="
                padding: 18px 20px;
                display: flex;
                flex-direction: column;
                gap: 6px;
                font-weight: 600;
                color: rgba(0, 0, 0, 0.68);
              "
            >
              <div style="font-size: 16px;">
                GlassSurface
              </div>
              <div style="font-size: 12px; opacity: 0.7;">
                Layered clarity with adjustable optics
              </div>
            </div>
          </TxGlassSurface>
        </div>
      </div>
    </div>

    <div
      style="
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
        gap: 12px 16px;
      "
    >
      <div>
        <div style="font-size: 12px; opacity: 0.72; margin-bottom: 6px;">
          width (px)
        </div>
        <TxSlider v-model="width" :min="200" :max="560" :step="10" show-value />
      </div>
      <div>
        <div style="font-size: 12px; opacity: 0.72; margin-bottom: 6px;">
          height (px)
        </div>
        <TxSlider v-model="height" :min="120" :max="320" :step="10" show-value />
      </div>
      <div>
        <div style="font-size: 12px; opacity: 0.72; margin-bottom: 6px;">
          borderRadius
        </div>
        <TxSlider v-model="borderRadius" :min="0" :max="64" :step="1" show-value />
      </div>
      <div>
        <div style="font-size: 12px; opacity: 0.72; margin-bottom: 6px;">
          borderWidth
        </div>
        <TxSlider v-model="borderWidth" :min="0" :max="0.3" :step="0.01" show-value />
      </div>
      <div>
        <div style="font-size: 12px; opacity: 0.72; margin-bottom: 6px;">
          brightness
        </div>
        <TxSlider v-model="brightness" :min="0" :max="140" :step="1" show-value />
      </div>
      <div>
        <div style="font-size: 12px; opacity: 0.72; margin-bottom: 6px;">
          opacity
        </div>
        <TxSlider v-model="opacity" :min="0" :max="1" :step="0.01" show-value />
      </div>
      <div>
        <div style="font-size: 12px; opacity: 0.72; margin-bottom: 6px;">
          blur (px)
        </div>
        <TxSlider v-model="blur" :min="0" :max="24" :step="1" show-value />
      </div>
      <div>
        <div style="font-size: 12px; opacity: 0.72; margin-bottom: 6px;">
          displace
        </div>
        <TxSlider v-model="displace" :min="0" :max="6" :step="0.1" show-value />
      </div>
      <div>
        <div style="font-size: 12px; opacity: 0.72; margin-bottom: 6px;">
          backgroundOpacity
        </div>
        <TxSlider v-model="backgroundOpacity" :min="0" :max="0.5" :step="0.01" show-value />
      </div>
      <div>
        <div style="font-size: 12px; opacity: 0.72; margin-bottom: 6px;">
          saturation
        </div>
        <TxSlider v-model="saturation" :min="0.5" :max="2.4" :step="0.05" show-value />
      </div>
      <div>
        <div style="font-size: 12px; opacity: 0.72; margin-bottom: 6px;">
          distortionScale
        </div>
        <TxSlider v-model="distortionScale" :min="-600" :max="0" :step="10" show-value />
      </div>
      <div>
        <div style="font-size: 12px; opacity: 0.72; margin-bottom: 6px;">
          redOffset
        </div>
        <TxSlider v-model="redOffset" :min="-80" :max="80" :step="1" show-value />
      </div>
      <div>
        <div style="font-size: 12px; opacity: 0.72; margin-bottom: 6px;">
          greenOffset
        </div>
        <TxSlider v-model="greenOffset" :min="-80" :max="80" :step="1" show-value />
      </div>
      <div>
        <div style="font-size: 12px; opacity: 0.72; margin-bottom: 6px;">
          blueOffset
        </div>
        <TxSlider v-model="blueOffset" :min="-80" :max="80" :step="1" show-value />
      </div>
    </div>

    <div style="display: flex; flex-wrap: wrap; gap: 16px;">
      <label style="display: flex; flex-direction: column; gap: 6px; font-size: 12px; opacity: 0.72;">
        xChannel
        <select v-model="xChannel" style="padding: 6px 8px; border-radius: 8px; border: 1px solid rgba(0,0,0,0.12);">
          <option v-for="channel in channels" :key="channel" :value="channel">
            {{ channel }}
          </option>
        </select>
      </label>
      <label style="display: flex; flex-direction: column; gap: 6px; font-size: 12px; opacity: 0.72;">
        yChannel
        <select v-model="yChannel" style="padding: 6px 8px; border-radius: 8px; border: 1px solid rgba(0,0,0,0.12);">
          <option v-for="channel in channels" :key="channel" :value="channel">
            {{ channel }}
          </option>
        </select>
      </label>
      <label style="display: flex; flex-direction: column; gap: 6px; font-size: 12px; opacity: 0.72; min-width: 180px;">
        mixBlendMode
        <select v-model="mixBlendMode" style="padding: 6px 8px; border-radius: 8px; border: 1px solid rgba(0,0,0,0.12);">
          <option v-for="mode in blendModes" :key="mode" :value="mode">
            {{ mode }}
          </option>
        </select>
      </label>
    </div>
  </div>
</template>

API

Props

PropTypeDefaultDescription
widthstring | number'200px'Surface width. Numeric values are normalized to px; strings are passed through.
heightstring | number'200px'Surface height. Numeric values are normalized to px; strings are passed through.
borderRadiusnumber20Outer radius in px, also applied to the generated displacement map rectangles.
borderWidthnumber0.07Edge-size multiplier used when generating the displacement map mask.
brightnessnumber70Lightness percentage for the generated inner displacement mask.
opacitynumber0.93Alpha value for the generated inner displacement mask.
blurnumber11Blur radius for the generated displacement mask; also drives the backdrop-filter fallback blur.
displacenumber0.5Final SVG feGaussianBlur.stdDeviation applied after the RGB displacement maps are blended.
backgroundOpacitynumber0Background alpha used on the SVG-filter rendering path.
saturationnumber1Saturation value appended to the SVG-filter backdrop-filter chain.
distortionScalenumber-180Base SVG displacement scale applied to the red, green, and blue maps.
redOffsetnumber0Offset added to distortionScale for the red displacement map.
greenOffsetnumber10Offset added to distortionScale for the green displacement map.
blueOffsetnumber20Offset added to distortionScale for the blue displacement map.
xChannel'R' | 'G' | 'B''R'SVG displacement X channel selector forwarded to all RGB maps.
yChannel'R' | 'G' | 'B''G'SVG displacement Y channel selector forwarded to all RGB maps.
mixBlendModestring'difference'CSS mix-blend-mode used by the generated blue gradient rectangle in the displacement map.

This component degrades based on browser capabilities (when SVG filter/backdrop-filter is unsupported).