feat(dees-screensaver): improve screensaver activation, visuals, and dismissal animations
This commit is contained in:
10
changelog.md
10
changelog.md
@@ -1,5 +1,15 @@
|
||||
# Changelog
|
||||
|
||||
## 2026-01-06 - 3.34.0 - feat(dees-screensaver)
|
||||
improve screensaver activation, visuals, and dismissal animations
|
||||
|
||||
- Add a safe demo flow that ensures only one screensaver instance: append an inactive element to document.body, store static instance, and activate after a 2s delay.
|
||||
- Introduce static instance management and destroy flow so multiple demo calls don't leak elements.
|
||||
- Rework DOM/CSS structure: add .screensaver-content container and .vignette element, separate backdrop, and cache element references for animations.
|
||||
- Adjust transitions and timings (overlay opacity, vignette transform, time opacity) for smoother entrance and staged reveal.
|
||||
- Implement a masked circular reveal dismissal: on click/touch, compute coordinates, animate a radial mask expanding from the pointer, dispatch screensaver-click with {x,y}, then remove the screensaver.
|
||||
- Reset mask on activation to support repeated activations without leftover masking styles.
|
||||
|
||||
## 2026-01-06 - 3.33.0 - feat(dees-statsgrid)
|
||||
add multiPercentage tile type to stats grid
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* autocreated commitinfo by @push.rocks/commitinfo
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@design.estate/dees-catalog',
|
||||
version: '3.33.0',
|
||||
name: '@ecobridge.xyz/catalog',
|
||||
version: '3.34.0',
|
||||
description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.'
|
||||
}
|
||||
|
||||
@@ -28,7 +28,27 @@ const colors = [
|
||||
|
||||
@customElement('dees-screensaver')
|
||||
export class DeesScreensaver extends DeesElement {
|
||||
public static demo = () => html`<dees-screensaver .active=${true}></dees-screensaver>`;
|
||||
public static demo = () => {
|
||||
// Clean up any existing instance first
|
||||
if (DeesScreensaver.instance) {
|
||||
DeesScreensaver.instance.remove();
|
||||
DeesScreensaver.instance = null;
|
||||
}
|
||||
|
||||
// Create screensaver element immediately but inactive
|
||||
const screensaver = document.createElement('dees-screensaver') as DeesScreensaver;
|
||||
document.body.appendChild(screensaver);
|
||||
DeesScreensaver.instance = screensaver;
|
||||
|
||||
// Activate after 2 seconds to show the animation
|
||||
setTimeout(() => {
|
||||
if (DeesScreensaver.instance === screensaver) {
|
||||
screensaver.active = true;
|
||||
}
|
||||
}, 2000);
|
||||
|
||||
return html`<div style="padding: 24px; color: #888;">Screensaver will activate in 2 seconds...</div>`;
|
||||
};
|
||||
|
||||
// Instance management
|
||||
private static instance: DeesScreensaver | null = null;
|
||||
@@ -72,7 +92,7 @@ export class DeesScreensaver extends DeesElement {
|
||||
z-index: ${zIndexLayers.overlay.screensaver};
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transition: opacity 0.8s ease;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
:host([active]) {
|
||||
@@ -87,6 +107,48 @@ export class DeesScreensaver extends DeesElement {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: hsl(240 10% 4%);
|
||||
opacity: 0;
|
||||
transition: opacity 1.5s ease 0.5s;
|
||||
}
|
||||
|
||||
:host([active]) .backdrop {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.vignette {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: radial-gradient(
|
||||
circle at center,
|
||||
transparent 0%,
|
||||
transparent 5%,
|
||||
hsla(240 10% 4% / 0.2) 20%,
|
||||
hsla(240 10% 4% / 0.6) 40%,
|
||||
hsla(240 10% 4% / 0.9) 60%,
|
||||
hsl(240 10% 4%) 80%
|
||||
);
|
||||
transform: scale(3);
|
||||
opacity: 0;
|
||||
transition:
|
||||
transform 2s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
opacity 0.5s ease;
|
||||
}
|
||||
|
||||
:host([active]) .vignette {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Container for all screensaver visuals - gets masked on dismiss */
|
||||
.screensaver-content {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.time-container {
|
||||
@@ -100,6 +162,14 @@ export class DeesScreensaver extends DeesElement {
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
will-change: transform;
|
||||
opacity: 0;
|
||||
transition: opacity 0.8s ease;
|
||||
transition-delay: 0s;
|
||||
}
|
||||
|
||||
:host([active]) .time-container {
|
||||
opacity: 1;
|
||||
transition-delay: 1.2s;
|
||||
}
|
||||
|
||||
.time {
|
||||
@@ -158,6 +228,8 @@ export class DeesScreensaver extends DeesElement {
|
||||
private elementHeight = 140;
|
||||
private hasBounced = false;
|
||||
private timeContainerEl: HTMLElement | null = null;
|
||||
private vignetteEl: HTMLElement | null = null;
|
||||
private contentEl: HTMLElement | null = null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@@ -166,16 +238,21 @@ export class DeesScreensaver extends DeesElement {
|
||||
|
||||
public render(): TemplateResult {
|
||||
return html`
|
||||
<div class="backdrop" @click=${this.handleClick}></div>
|
||||
<div class="time-container">
|
||||
<span class="time" style="color: ${this.currentColor};">${this.currentTime}</span>
|
||||
<span class="date" style="color: ${this.currentColor};">${this.currentDate}</span>
|
||||
<div class="screensaver-content" @click=${this.handleClick}>
|
||||
<div class="backdrop"></div>
|
||||
<div class="vignette"></div>
|
||||
<div class="time-container">
|
||||
<span class="time" style="color: ${this.currentColor};">${this.currentTime}</span>
|
||||
<span class="date" style="color: ${this.currentColor};">${this.currentDate}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
public firstUpdated(): void {
|
||||
this.timeContainerEl = this.shadowRoot?.querySelector('.time-container') as HTMLElement;
|
||||
this.vignetteEl = this.shadowRoot?.querySelector('.vignette') as HTMLElement;
|
||||
this.contentEl = this.shadowRoot?.querySelector('.screensaver-content') as HTMLElement;
|
||||
}
|
||||
|
||||
async connectedCallback(): Promise<void> {
|
||||
@@ -194,6 +271,11 @@ export class DeesScreensaver extends DeesElement {
|
||||
super.updated(changedProperties);
|
||||
if (changedProperties.has('active')) {
|
||||
if (this.active) {
|
||||
// Reset mask for fresh activation
|
||||
if (this.contentEl) {
|
||||
this.contentEl.style.maskImage = '';
|
||||
this.contentEl.style.webkitMaskImage = '';
|
||||
}
|
||||
this.startAnimation();
|
||||
this.startTimeUpdate();
|
||||
} else {
|
||||
@@ -311,9 +393,54 @@ export class DeesScreensaver extends DeesElement {
|
||||
}
|
||||
}
|
||||
|
||||
private handleClick(): void {
|
||||
this.dispatchEvent(new CustomEvent('screensaver-click'));
|
||||
// Optionally hide on click
|
||||
this.active = false;
|
||||
private handleClick(event: MouseEvent | TouchEvent): void {
|
||||
// Get click/touch position
|
||||
let x: number, y: number;
|
||||
|
||||
if (event instanceof TouchEvent && event.changedTouches.length > 0) {
|
||||
x = event.changedTouches[0].clientX;
|
||||
y = event.changedTouches[0].clientY;
|
||||
} else if (event instanceof MouseEvent) {
|
||||
x = event.clientX;
|
||||
y = event.clientY;
|
||||
} else {
|
||||
// Fallback to center
|
||||
x = window.innerWidth / 2;
|
||||
y = window.innerHeight / 2;
|
||||
}
|
||||
|
||||
this.dispatchEvent(new CustomEvent('screensaver-click', { detail: { x, y } }));
|
||||
|
||||
// Animate circle reveal from click position
|
||||
if (this.contentEl) {
|
||||
const duration = 700; // ms - slower for dramatic effect
|
||||
const startTime = performance.now();
|
||||
const maxSize = Math.max(window.innerWidth, window.innerHeight) * 1.5;
|
||||
const startSize = 20; // Start with small visible circle
|
||||
|
||||
const animate = (currentTime: number) => {
|
||||
const elapsed = currentTime - startTime;
|
||||
const progress = Math.min(elapsed / duration, 1);
|
||||
|
||||
// Ease-in curve: starts slow, picks up speed
|
||||
const eased = progress * progress * progress;
|
||||
const size = startSize + eased * (maxSize - startSize);
|
||||
|
||||
this.contentEl!.style.maskImage = `radial-gradient(circle ${size}px at ${x}px ${y}px, transparent 100%, black 100%)`;
|
||||
this.contentEl!.style.webkitMaskImage = `radial-gradient(circle ${size}px at ${x}px ${y}px, transparent 100%, black 100%)`;
|
||||
|
||||
if (progress < 1) {
|
||||
requestAnimationFrame(animate);
|
||||
} else {
|
||||
// Animation complete - remove screensaver
|
||||
this.active = false;
|
||||
DeesScreensaver.destroy();
|
||||
}
|
||||
};
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
} else {
|
||||
this.active = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user