mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-03-07 14:39:30 -07:00
Migrated 'Glass Surface' component.
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
// Highlighted sidebar items
|
// Highlighted sidebar items
|
||||||
export const NEW = ['Target Cursor', 'Ripple Grid', 'Magic Bento', 'Galaxy'];
|
export const NEW = ['Target Cursor', 'Ripple Grid', 'Magic Bento', 'Galaxy', 'Glass Surface'];
|
||||||
export const UPDATED = [];
|
export const UPDATED = [];
|
||||||
|
|
||||||
// Used for main sidebar navigation
|
// Used for main sidebar navigation
|
||||||
@@ -59,6 +59,7 @@ export const CATEGORIES = [
|
|||||||
subcategories: [
|
subcategories: [
|
||||||
'Animated List',
|
'Animated List',
|
||||||
'Masonry',
|
'Masonry',
|
||||||
|
'Glass Surface',
|
||||||
'Magic Bento',
|
'Magic Bento',
|
||||||
'Profile Card',
|
'Profile Card',
|
||||||
'Dock',
|
'Dock',
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ const textAnimations = {
|
|||||||
const components = {
|
const components = {
|
||||||
'animated-list': () => import('../demo/Components/AnimatedListDemo.vue'),
|
'animated-list': () => import('../demo/Components/AnimatedListDemo.vue'),
|
||||||
'masonry': () => import('../demo/Components/MasonryDemo.vue'),
|
'masonry': () => import('../demo/Components/MasonryDemo.vue'),
|
||||||
|
'glass-surface': () => import('../demo/Components/GlassSurfaceDemo.vue'),
|
||||||
'magic-bento': () => import('../demo/Components/MagicBentoDemo.vue'),
|
'magic-bento': () => import('../demo/Components/MagicBentoDemo.vue'),
|
||||||
'profile-card': () => import('../demo/Components/ProfileCardDemo.vue'),
|
'profile-card': () => import('../demo/Components/ProfileCardDemo.vue'),
|
||||||
'dock': () => import('../demo/Components/DockDemo.vue'),
|
'dock': () => import('../demo/Components/DockDemo.vue'),
|
||||||
|
|||||||
17
src/constants/code/Components/glassSurfaceCode.ts
Normal file
17
src/constants/code/Components/glassSurfaceCode.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import code from '@content/Components/GlassSurface/GlassSurface.vue?raw';
|
||||||
|
import { createCodeObject } from '../../../types/code';
|
||||||
|
|
||||||
|
export const glassSurface = createCodeObject(code, 'Components/GlassSurface', {
|
||||||
|
usage: `<template>
|
||||||
|
<GlassSurface
|
||||||
|
:width="300"
|
||||||
|
:height="200"
|
||||||
|
:border-radius="24"
|
||||||
|
style="custom-style"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import GlassSurface from "./GlassSurface.vue";
|
||||||
|
</script>`
|
||||||
|
});
|
||||||
313
src/content/Components/GlassSurface/GlassSurface.vue
Normal file
313
src/content/Components/GlassSurface/GlassSurface.vue
Normal file
@@ -0,0 +1,313 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
ref="containerRef"
|
||||||
|
class="glass-surface"
|
||||||
|
:class="{
|
||||||
|
'glass-surface--svg': supportsSVGFilters,
|
||||||
|
'glass-surface--fallback': !supportsSVGFilters
|
||||||
|
}"
|
||||||
|
:style="containerStyle"
|
||||||
|
>
|
||||||
|
<svg class="glass-surface__filter" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs>
|
||||||
|
<filter :id="filterId" colorInterpolationFilters="sRGB" x="0%" y="0%" width="100%" height="100%">
|
||||||
|
<feImage ref="feImageRef" x="0" y="0" width="100%" height="100%" preserveAspectRatio="none" result="map" />
|
||||||
|
|
||||||
|
<feDisplacementMap ref="redChannelRef" in="SourceGraphic" in2="map" id="redchannel" result="dispRed" />
|
||||||
|
<feColorMatrix
|
||||||
|
in="dispRed"
|
||||||
|
type="matrix"
|
||||||
|
values="1 0 0 0 0
|
||||||
|
0 0 0 0 0
|
||||||
|
0 0 0 0 0
|
||||||
|
0 0 0 1 0"
|
||||||
|
result="red"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<feDisplacementMap ref="greenChannelRef" in="SourceGraphic" in2="map" id="greenchannel" result="dispGreen" />
|
||||||
|
<feColorMatrix
|
||||||
|
in="dispGreen"
|
||||||
|
type="matrix"
|
||||||
|
values="0 0 0 0 0
|
||||||
|
0 1 0 0 0
|
||||||
|
0 0 0 0 0
|
||||||
|
0 0 0 1 0"
|
||||||
|
result="green"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<feDisplacementMap ref="blueChannelRef" in="SourceGraphic" in2="map" id="bluechannel" result="dispBlue" />
|
||||||
|
<feColorMatrix
|
||||||
|
in="dispBlue"
|
||||||
|
type="matrix"
|
||||||
|
values="0 0 0 0 0
|
||||||
|
0 0 0 0 0
|
||||||
|
0 0 1 0 0
|
||||||
|
0 0 0 1 0"
|
||||||
|
result="blue"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<feBlend in="red" in2="green" mode="screen" result="rg" />
|
||||||
|
<feBlend in="rg" in2="blue" mode="screen" result="output" />
|
||||||
|
<feGaussianBlur ref="gaussianBlurRef" in="output" stdDeviation="0.7" />
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, type CSSProperties, useTemplateRef, onMounted, computed } from 'vue';
|
||||||
|
|
||||||
|
interface GlassSurfaceProps {
|
||||||
|
width?: string | number;
|
||||||
|
height?: string | number;
|
||||||
|
borderRadius?: number;
|
||||||
|
borderWidth?: number;
|
||||||
|
brightness?: number;
|
||||||
|
opacity?: number;
|
||||||
|
blur?: number;
|
||||||
|
displace?: number;
|
||||||
|
backgroundOpacity?: number;
|
||||||
|
saturation?: number;
|
||||||
|
distortionScale?: number;
|
||||||
|
redOffset?: number;
|
||||||
|
greenOffset?: number;
|
||||||
|
blueOffset?: number;
|
||||||
|
xChannel?: 'R' | 'G' | 'B';
|
||||||
|
yChannel?: 'R' | 'G' | 'B';
|
||||||
|
mixBlendMode?:
|
||||||
|
| 'normal'
|
||||||
|
| 'multiply'
|
||||||
|
| 'screen'
|
||||||
|
| 'overlay'
|
||||||
|
| 'darken'
|
||||||
|
| 'lighten'
|
||||||
|
| 'color-dodge'
|
||||||
|
| 'color-burn'
|
||||||
|
| 'hard-light'
|
||||||
|
| 'soft-light'
|
||||||
|
| 'difference'
|
||||||
|
| 'exclusion'
|
||||||
|
| 'hue'
|
||||||
|
| 'saturation'
|
||||||
|
| 'color'
|
||||||
|
| 'luminosity'
|
||||||
|
| 'plus-darker'
|
||||||
|
| 'plus-lighter';
|
||||||
|
className?: string;
|
||||||
|
style?: CSSProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<GlassSurfaceProps>(), {
|
||||||
|
width: '200px',
|
||||||
|
height: '200px',
|
||||||
|
borderRadius: 20,
|
||||||
|
borderWidth: 0.07,
|
||||||
|
brightness: 70,
|
||||||
|
opacity: 0.93,
|
||||||
|
blur: 11,
|
||||||
|
displace: 0.5,
|
||||||
|
backgroundOpacity: 0,
|
||||||
|
saturation: 1,
|
||||||
|
distortionScale: -80,
|
||||||
|
redOffset: 0,
|
||||||
|
greenOffset: 10,
|
||||||
|
blueOffset: 20,
|
||||||
|
xChannel: 'R',
|
||||||
|
yChannel: 'G',
|
||||||
|
mixBlendMode: 'difference',
|
||||||
|
className: '',
|
||||||
|
style: () => ({})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generate unique IDs for SVG elements
|
||||||
|
const generateUniqueId = (prefix: string): string => {
|
||||||
|
return `${prefix}-${Math.random().toString(36).substring(2, 15)}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterId = ref(generateUniqueId('glass-surface-filter'));
|
||||||
|
const redGradId = ref(generateUniqueId('red-grad'));
|
||||||
|
const blueGradId = ref(generateUniqueId('blue-grad'));
|
||||||
|
|
||||||
|
const containerRef = useTemplateRef<HTMLDivElement>('containerRef');
|
||||||
|
const feImageRef = useTemplateRef<SVGSVGElement>('feImageRef');
|
||||||
|
const redChannelRef = useTemplateRef<SVGSVGElement>('redChannelRef');
|
||||||
|
const greenChannelRef = useTemplateRef<SVGSVGElement>('greenChannelRef');
|
||||||
|
const blueChannelRef = useTemplateRef<SVGSVGElement>('blueChannelRef');
|
||||||
|
const gaussianBlurRef = useTemplateRef<SVGSVGElement>('gaussianBlurRef');
|
||||||
|
|
||||||
|
const generateDisplacementMap = () => {
|
||||||
|
const rect = containerRef.value?.getBoundingClientRect();
|
||||||
|
const actualWidth = rect?.width || 400;
|
||||||
|
const actualHeight = rect?.height || 200;
|
||||||
|
const edgeSize = Math.min(actualWidth, actualHeight) * (props.borderWidth * 0.5);
|
||||||
|
|
||||||
|
const svgContent = `
|
||||||
|
<svg viewBox="0 0 ${actualWidth} ${actualHeight}" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="${redGradId.value}" x1="100%" y1="0%" x2="0%" y2="0%">
|
||||||
|
<stop offset="0%" stop-color="#0000"/>
|
||||||
|
<stop offset="100%" stop-color="red"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="${blueGradId.value}" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||||
|
<stop offset="0%" stop-color="#0000"/>
|
||||||
|
<stop offset="100%" stop-color="blue"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect x="0" y="0" width="${actualWidth}" height="${actualHeight}" fill="black"></rect>
|
||||||
|
<rect x="0" y="0" width="${actualWidth}" height="${actualHeight}" rx="${props.borderRadius}" fill="url(#${redGradId.value})" />
|
||||||
|
<rect x="0" y="0" width="${actualWidth}" height="${actualHeight}" rx="${props.borderRadius}" fill="url(#${blueGradId.value})" style="mix-blend-mode: ${props.mixBlendMode}" />
|
||||||
|
<rect x="${edgeSize}" y="${edgeSize}" width="${actualWidth - edgeSize * 2}" height="${actualHeight - edgeSize * 2}" rx="${props.borderRadius}" fill="hsl(0 0% ${props.brightness}% / ${props.opacity})" style="filter:blur(${props.blur}px)" />
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
|
||||||
|
return `data:image/svg+xml,${encodeURIComponent(svgContent)}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateDisplacementMap = () => {
|
||||||
|
feImageRef.value?.setAttribute('href', generateDisplacementMap());
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
updateDisplacementMap();
|
||||||
|
|
||||||
|
if (containerRef.value) {
|
||||||
|
const resizeObserver = new ResizeObserver(() => {
|
||||||
|
setTimeout(updateDisplacementMap, 0);
|
||||||
|
});
|
||||||
|
resizeObserver.observe(containerRef.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[
|
||||||
|
{ ref: redChannelRef, offset: props.redOffset },
|
||||||
|
{ ref: greenChannelRef, offset: props.greenOffset },
|
||||||
|
{ ref: blueChannelRef, offset: props.blueOffset }
|
||||||
|
].forEach(({ ref, offset }) => {
|
||||||
|
ref.value?.setAttribute('scale', (props.distortionScale + offset).toString());
|
||||||
|
ref.value?.setAttribute('xChannelSelector', props.xChannel);
|
||||||
|
ref.value?.setAttribute('yChannelSelector', props.yChannel);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (gaussianBlurRef.value) {
|
||||||
|
gaussianBlurRef.value.setAttribute('stdDeviation', props.displace.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const supportsSVGFilters = () => {
|
||||||
|
const ua = navigator.userAgent;
|
||||||
|
const isWebkit = /Safari/.test(ua) && !/Chrome/.test(ua);
|
||||||
|
const isFirefox = /Firefox/.test(ua);
|
||||||
|
|
||||||
|
if (isWebkit || isFirefox) return false;
|
||||||
|
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.style.backdropFilter = `url(#${filterId.value})`;
|
||||||
|
return div.style.backdropFilter !== '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const containerStyle = computed(() => ({
|
||||||
|
...props.style,
|
||||||
|
width: typeof props.width === 'number' ? `${props.width}px` : props.width,
|
||||||
|
height: typeof props.height === 'number' ? `${props.height}px` : props.height,
|
||||||
|
borderRadius: `${props.borderRadius}px`,
|
||||||
|
'--glass-frost': props.backgroundOpacity,
|
||||||
|
'--glass-saturation': props.saturation,
|
||||||
|
'--filter-id': `url(#${filterId.value})`
|
||||||
|
}));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.glass-surface {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: opacity 0.26s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass-surface__filter {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
pointer-events: none;
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
opacity: 0;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass-surface--svg {
|
||||||
|
background: light-dark(hsl(0 0% 100% / var(--glass-frost, 0)), hsl(0 0% 0% / var(--glass-frost, 0)));
|
||||||
|
backdrop-filter: var(--filter-id, url(#glass-filter)) saturate(var(--glass-saturation, 1));
|
||||||
|
box-shadow:
|
||||||
|
0 0 2px 1px light-dark(color-mix(in oklch, black, transparent 85%), color-mix(in oklch, white, transparent 65%))
|
||||||
|
inset,
|
||||||
|
0 0 10px 4px light-dark(color-mix(in oklch, black, transparent 90%), color-mix(in oklch, white, transparent 85%))
|
||||||
|
inset,
|
||||||
|
0px 4px 16px rgba(17, 17, 26, 0.05),
|
||||||
|
0px 8px 24px rgba(17, 17, 26, 0.05),
|
||||||
|
0px 16px 56px rgba(17, 17, 26, 0.05),
|
||||||
|
0px 4px 16px rgba(17, 17, 26, 0.05) inset,
|
||||||
|
0px 8px 24px rgba(17, 17, 26, 0.05) inset,
|
||||||
|
0px 16px 56px rgba(17, 17, 26, 0.05) inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass-surface--fallback {
|
||||||
|
background: rgba(255, 255, 255, 0.25);
|
||||||
|
backdrop-filter: blur(12px) saturate(1.8) brightness(1.1);
|
||||||
|
-webkit-backdrop-filter: blur(12px) saturate(1.8) brightness(1.1);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||||
|
box-shadow:
|
||||||
|
0 8px 32px 0 rgba(31, 38, 135, 0.2),
|
||||||
|
0 2px 16px 0 rgba(31, 38, 135, 0.1),
|
||||||
|
inset 0 1px 0 0 rgba(255, 255, 255, 0.4),
|
||||||
|
inset 0 -1px 0 0 rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.glass-surface--fallback {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
backdrop-filter: blur(12px) saturate(1.8) brightness(1.2);
|
||||||
|
-webkit-backdrop-filter: blur(12px) saturate(1.8) brightness(1.2);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1px 0 0 rgba(255, 255, 255, 0.2),
|
||||||
|
inset 0 -1px 0 0 rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports not (backdrop-filter: blur(10px)) {
|
||||||
|
.glass-surface--fallback {
|
||||||
|
background: rgba(255, 255, 255, 0.4);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1px 0 0 rgba(255, 255, 255, 0.5),
|
||||||
|
inset 0 -1px 0 0 rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass-surface--fallback::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(255, 255, 255, 0.15);
|
||||||
|
border-radius: inherit;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports not (backdrop-filter: blur(10px)) {
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.glass-surface--fallback {
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass-surface--fallback::before {
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass-surface:focus-visible {
|
||||||
|
outline: 2px solid light-dark(#007aff, #0a84ff);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
261
src/demo/Components/GlassSurfaceDemo.vue
Normal file
261
src/demo/Components/GlassSurfaceDemo.vue
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
<template>
|
||||||
|
<TabbedLayout>
|
||||||
|
<template #preview>
|
||||||
|
<div class="relative overflow-y-auto no-scrollbar demo-container">
|
||||||
|
<GlassSurface
|
||||||
|
:key="key"
|
||||||
|
:width="360"
|
||||||
|
:height="100"
|
||||||
|
:border-radius="borderRadius"
|
||||||
|
:background-opacity="backgroundOpacity"
|
||||||
|
:saturation="saturation"
|
||||||
|
:border-width="borderWidth"
|
||||||
|
:brightness="brightness"
|
||||||
|
:opacity="opacity"
|
||||||
|
:blur="blur"
|
||||||
|
:displace="displace"
|
||||||
|
:distortion-scale="distortionScale"
|
||||||
|
:red-offset="redOffset"
|
||||||
|
:green-offset="greenOffset"
|
||||||
|
:blue-offset="blueOffset"
|
||||||
|
style="position: sticky; top: 50%; transform: translateY(-50%); z-index: 10"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="absolute flex flex-col items-center gap-6 top-0 left-0 right-0">
|
||||||
|
<div
|
||||||
|
class="absolute translate-y-1/2 top-12 text-4xl font-bold text-[#27FF64] z-0 whitespace-nowrap text-center"
|
||||||
|
>
|
||||||
|
Try scrolling.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Top Spacer -->
|
||||||
|
<div class="h-60 w-full" />
|
||||||
|
|
||||||
|
<!-- Image Blocks -->
|
||||||
|
<div v-for="(item, index) in imageBlocks" :key="index" class="relative">
|
||||||
|
<img :src="item.src" class="w-128 rounded-2xl object-cover grayscale-100" />
|
||||||
|
<div
|
||||||
|
class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 font-extrabold text-center leading-[100%] text-[3rem] min-w-72"
|
||||||
|
>
|
||||||
|
{{ item.text }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Bottom Spacer -->
|
||||||
|
<div class="h-60 w-full" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Customize>
|
||||||
|
<PreviewSlider title="Border Radius" v-model="borderRadius" :min="0" :max="50" :step="1" />
|
||||||
|
<PreviewSlider title="Background Opacity" v-model="backgroundOpacity" :min="0" :max="1" :step="0.01" />
|
||||||
|
<PreviewSlider title="Saturation" v-model="saturation" :min="0" :max="3" :step="1" />
|
||||||
|
<PreviewSlider title="Border Width" v-model="borderWidth" :min="0" :max="0.2" :step="0.01" />
|
||||||
|
<PreviewSlider title="Brightness" v-model="brightness" :min="0" :max="100" :step="1" />
|
||||||
|
<PreviewSlider title="Opacity" v-model="opacity" :min="0" :max="1" :step="0.01" />
|
||||||
|
<PreviewSlider title="Blur" v-model="blur" :min="0" :max="30" :step="1" />
|
||||||
|
<PreviewSlider title="Displace" v-model="displace" :min="0" :max="5" :step="0.1" />
|
||||||
|
<PreviewSlider title="Distortion Scale" v-model="distortionScale" :min="-300" :max="300" :step="1" />
|
||||||
|
<PreviewSlider title="Red Offset" v-model="redOffset" :min="-50" :max="50" :step="1" />
|
||||||
|
<PreviewSlider title="Green Offset" v-model="greenOffset" :min="-50" :max="50" :step="1" />
|
||||||
|
<PreviewSlider title="Blue Offset" v-model="blueOffset" :min="-50" :max="50" :step="1" />
|
||||||
|
</Customize>
|
||||||
|
|
||||||
|
<PropTable :data="propData" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #code>
|
||||||
|
<CodeExample :code-object="glassSurface" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cli>
|
||||||
|
<CliInstallation :command="glassSurface.cli" />
|
||||||
|
</template>
|
||||||
|
</TabbedLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
import TabbedLayout from '../../components/common/TabbedLayout.vue';
|
||||||
|
import PropTable from '../../components/common/PropTable.vue';
|
||||||
|
import CliInstallation from '../../components/code/CliInstallation.vue';
|
||||||
|
import CodeExample from '../../components/code/CodeExample.vue';
|
||||||
|
import Customize from '../../components/common/Customize.vue';
|
||||||
|
import PreviewSlider from '../../components/common/PreviewSlider.vue';
|
||||||
|
import GlassSurface from '../../content/Components/GlassSurface/GlassSurface.vue';
|
||||||
|
import { glassSurface } from '@/constants/code/Components/glassSurfaceCode';
|
||||||
|
import { useForceRerender } from '@/composables/useForceRerender';
|
||||||
|
|
||||||
|
const { rerenderKey: key, forceRerender } = useForceRerender();
|
||||||
|
|
||||||
|
const borderRadius = ref(50);
|
||||||
|
const backgroundOpacity = ref(0.1);
|
||||||
|
const saturation = ref(1);
|
||||||
|
const borderWidth = ref(0.07);
|
||||||
|
const brightness = ref(50);
|
||||||
|
const opacity = ref(0.93);
|
||||||
|
const blur = ref(11);
|
||||||
|
const displace = ref(0.5);
|
||||||
|
const distortionScale = ref(-80);
|
||||||
|
const redOffset = ref(0);
|
||||||
|
const greenOffset = ref(10);
|
||||||
|
const blueOffset = ref(20);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => [
|
||||||
|
borderWidth.value,
|
||||||
|
brightness.value,
|
||||||
|
opacity.value,
|
||||||
|
blur.value,
|
||||||
|
displace.value,
|
||||||
|
distortionScale.value,
|
||||||
|
redOffset.value,
|
||||||
|
greenOffset.value,
|
||||||
|
blueOffset.value
|
||||||
|
],
|
||||||
|
() => {
|
||||||
|
forceRerender();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const imageBlocks = [
|
||||||
|
{
|
||||||
|
src: 'https://images.unsplash.com/photo-1500673587002-1d2548cfba1b?q=80&w=1740&auto=format&fit=crop',
|
||||||
|
text: 'The Summer Of Glass'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: 'https://images.unsplash.com/photo-1594576547505-1be67997401e?q=80&w=1932&auto=format&fit=crop',
|
||||||
|
text: 'Can Hold Any Content'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: 'https://images.unsplash.com/photo-1543127172-4b33cb699e35?q=80&w=1674&auto=format&fit=crop',
|
||||||
|
text: 'Has Built-In Fallback'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const propData = [
|
||||||
|
{
|
||||||
|
name: 'width',
|
||||||
|
type: 'number | string',
|
||||||
|
default: '200',
|
||||||
|
description: "Width of the glass surface (pixels or CSS value like '100%')"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'height',
|
||||||
|
type: 'number | string',
|
||||||
|
default: '80',
|
||||||
|
description: "Height of the glass surface (pixels or CSS value like '100vh')"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'borderRadius',
|
||||||
|
type: 'number',
|
||||||
|
default: '20',
|
||||||
|
description: 'Border radius in pixels'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'borderWidth',
|
||||||
|
type: 'number',
|
||||||
|
default: '0.07',
|
||||||
|
description: 'Border width factor for displacement map'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'brightness',
|
||||||
|
type: 'number',
|
||||||
|
default: '50',
|
||||||
|
description: 'Brightness percentage for displacement map'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'opacity',
|
||||||
|
type: 'number',
|
||||||
|
default: '0.93',
|
||||||
|
description: 'Opacity of displacement map elements'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'blur',
|
||||||
|
type: 'number',
|
||||||
|
default: '11',
|
||||||
|
description: 'Input blur amount in pixels'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'displace',
|
||||||
|
type: 'number',
|
||||||
|
default: '0',
|
||||||
|
description: 'Output blur (stdDeviation)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'backgroundOpacity',
|
||||||
|
type: 'number',
|
||||||
|
default: '0',
|
||||||
|
description: 'Background frost opacity (0-1)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'saturation',
|
||||||
|
type: 'number',
|
||||||
|
default: '1',
|
||||||
|
description: 'Backdrop filter saturation factor'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'distortionScale',
|
||||||
|
type: 'number',
|
||||||
|
default: '-180',
|
||||||
|
description: 'Main displacement scale'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'redOffset',
|
||||||
|
type: 'number',
|
||||||
|
default: '0',
|
||||||
|
description: 'Red channel extra displacement offset'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'greenOffset',
|
||||||
|
type: 'number',
|
||||||
|
default: '10',
|
||||||
|
description: 'Green channel extra displacement offset'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'blueOffset',
|
||||||
|
type: 'number',
|
||||||
|
default: '20',
|
||||||
|
description: 'Blue channel extra displacement offset'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'xChannel',
|
||||||
|
type: "'R' | 'G' | 'B'",
|
||||||
|
default: "'R'",
|
||||||
|
description: 'X displacement channel selector'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'yChannel',
|
||||||
|
type: "'R' | 'G' | 'B'",
|
||||||
|
default: "'G'",
|
||||||
|
description: 'Y displacement channel selector'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mixBlendMode',
|
||||||
|
type: 'BlendMode',
|
||||||
|
default: "'difference'",
|
||||||
|
description: 'Mix blend mode for displacement map'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'className',
|
||||||
|
type: 'string',
|
||||||
|
default: "''",
|
||||||
|
description: 'Additional CSS class names'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'style',
|
||||||
|
type: 'CSSProperties',
|
||||||
|
default: '{}',
|
||||||
|
description: 'Inline styles object'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.demo-container {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.demo-container::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user