animation

This commit is contained in:
Jordan Vidrine 2025-01-03 16:51:46 -06:00
parent a36c61c92c
commit c04ca33c1d
18 changed files with 153 additions and 39 deletions

View File

@ -1,8 +1,8 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
export default class WordCloud extends Component { export default class WordCloud extends Component {
get randomModifier() { get randomStyle() {
return Math.random(); return `--rand: ${Math.random()}`;
} }
<template> <template>
@ -10,13 +10,16 @@ export default class WordCloud extends Component {
<h3 class="rewind-report-title">Most used words</h3> <h3 class="rewind-report-title">Most used words</h3>
<div class="rewind-report-container"> <div class="rewind-report-container">
{{#each-in @report.data as |word count|}} {{#each-in @report.data as |word count|}}
<div <div class="rewind-card__wrapper" style={{this.randomStyle}}>
class="rewind-card__wrapper" <div class="rewind-card__inner">
style="--rand: {{this.randomModifier}}" <div class="rewind-card -front">
> <span class="rewind-card__title">?</span>
<div class="rewind-card"> <span class="rewind-card__data">{{count}}x</span>
</div>
<div class="rewind-card -back">
<span class="rewind-card__title">{{word}}</span> <span class="rewind-card__title">{{word}}</span>
<span class="rewind-card__data">{{count}} times</span> <span class="rewind-card__data">used {{count}} times</span>
</div>
</div> </div>
</div> </div>
{{/each-in}} {{/each-in}}

View File

@ -28,6 +28,14 @@ export default class Rewind extends Component {
@tracked fullScreen = true; @tracked fullScreen = true;
@tracked loadingRewind = false; @tracked loadingRewind = false;
@tracked shouldAnimate = false; // Controls animation state
@tracked imagesLoaded = false;
imageCache = [];
imageIndex = 1;
frameCount = 11;
animationFrameId = null;
lastScrollPosition = 0;
scrollThreshold = 6; // How many pixels to scroll before frame change
@action @action
registerScrollWrapper(element) { registerScrollWrapper(element) {
@ -46,6 +54,51 @@ export default class Rewind extends Component {
} }
} }
@action
preLoadImages() {
const preloadPromises = [];
for (let i = 0; i < this.frameCount; i++) {
const img = new Image();
img.src = `/plugins/discourse-rewind/images/bg-frames/bg-2_${i + 1}.png`;
this.imageCache[i] = img;
preloadPromises.push(
new Promise((resolve) => {
img.onload = resolve;
})
);
}
// Wait for all images to load
Promise.all(preloadPromises).then(() => {
this.imagesLoaded = true;
});
}
@action
updateBackground() {
// Only continue if we should be animating
if (this.shouldAnimate) {
const children =
this.rewindContainer.getElementsByClassName("background-2");
if (children.length > 0 && this.imageCache[this.imageIndex]) {
const imageUrl = this.imageCache[this.imageIndex].src;
children[0].style.background = `url(${imageUrl})`;
children[0].style.backgroundSize = "contain";
// Update image index
this.imageIndex = (this.imageIndex + 1) % this.frameCount;
}
// Schedule the next frame
this.animationFrameId = requestAnimationFrame(() =>
this.updateBackground()
);
}
}
@action @action
toggleFullScreen() { toggleFullScreen() {
this.fullScreen = !this.fullScreen; this.fullScreen = !this.fullScreen;
@ -61,6 +114,7 @@ export default class Rewind extends Component {
@action @action
handleScroll({ target }) { handleScroll({ target }) {
let children = this.rewindContainer.getElementsByClassName("parallax-bg"); let children = this.rewindContainer.getElementsByClassName("parallax-bg");
for (let i = 0; i < children.length; i++) { for (let i = 0; i < children.length; i++) {
children[i].style.transform = `translateY(-${ children[i].style.transform = `translateY(-${
(target.scrollTop * (i + 1)) / 5 (target.scrollTop * (i + 1)) / 5
@ -80,6 +134,7 @@ export default class Rewind extends Component {
(if this.fullScreen "-fullscreen") (if this.fullScreen "-fullscreen")
}} }}
{{didInsert this.loadRewind}} {{didInsert this.loadRewind}}
{{didInsert this.preLoadImages}}
{{on "keydown" this.handleEscape}} {{on "keydown" this.handleEscape}}
{{didInsert this.registerRewindContainer}} {{didInsert this.registerRewindContainer}}
tabindex="0" tabindex="0"
@ -87,7 +142,7 @@ export default class Rewind extends Component {
<div class="rewind"> <div class="rewind">
<div class="background-1 parallax-bg"></div> <div class="background-1 parallax-bg"></div>
<div class="background-2 parallax-bg"></div> <canvas class="background-2 parallax-bg"></canvas>
{{#if this.loadingRewind}} {{#if this.loadingRewind}}
<div class="rewind-loader"> <div class="rewind-loader">
<div class="spinner small"></div> <div class="spinner small"></div>

View File

@ -10,12 +10,15 @@ body {
.rewind-report-container { .rewind-report-container {
perspective: 1000px; perspective: 1000px;
transform-style: preserve-3d; transform-style: preserve-3d;
gap: 0.5em;
} }
.rewind-card__wrapper { .rewind-card__wrapper {
width: 150px;
height: 100px;
perspective: 1000px;
will-change: transform; will-change: transform;
transform-style: preserve-3d; transform-style: preserve-3d;
background-color: hsla(0deg, 0%, 0%, 10%);
border-radius: 0.25vw; border-radius: 0.25vw;
cursor: pointer; cursor: pointer;
animation-name: ambientMovement; animation-name: ambientMovement;
@ -24,15 +27,52 @@ body {
animation-iteration-count: infinite; animation-iteration-count: infinite;
animation-timing-function: ease-in-out; animation-timing-function: ease-in-out;
transform: rotateZ(calc(var(--ambientAmount) * 10deg)); transform: rotateZ(calc(var(--ambientAmount) * 10deg));
&__title { z-index: 1;
font-weight: 600; .rewind-card__inner {
position: relative;
width: 100%;
height: 100%;
text-align: center;
transform-style: preserve-3d;
transform: rotateY(0) scale(1);
} }
&__data { &:hover {
font-family: monospace; z-index: 999;
font-weight: normal;
font-size: 14px;
} }
.rewind-card { &:hover .rewind-card__inner {
animation-name: flip-zoom;
animation-direction: normal;
animation-fill-mode: both;
animation-timing-function: cubic-bezier(0.73, 0.42, 0.03, 0.78);
animation-duration: 0.5s;
}
.rewind-card.-back {
transform: rotateY(180deg);
}
.rewind-card.-front,
.rewind-card.-back {
position: absolute;
width: 100%;
height: 100%;
-webkit-backface-visibility: hidden; /* Safari */
backface-visibility: hidden;
}
}
@keyframes flip-zoom {
0% {
transform: rotateY(0) scale(1);
}
50% {
transform: rotateY(0) scale(0.9);
}
100% {
transform: rotateY(180deg) scale(1.25);
}
}
.rewind-card {
background-color: var(--secondary);
box-shadow: 0 0 0 1px var(--primary-200); box-shadow: 0 0 0 1px var(--primary-200);
border-radius: var(--d-border-radius); border-radius: var(--d-border-radius);
display: flex; display: flex;
@ -42,19 +82,13 @@ body {
box-sizing: border-box; box-sizing: border-box;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background: var(--secondary); &__title {
will-change: transform; font-weight: 600;
position: relative;
transform-style: preserve-3d;
} }
&:hover .rewind-card { &__data {
animation-name: easeOutElastic; font-family: monospace;
animation-duration: 400ms; font-weight: normal;
animation-fill-mode: forwards; font-size: 14px;
transform: translateZ(calc(var(--easeAmount) * 50px));
box-shadow: 0 0.15em calc(0.5em + (var(--easeAmount) * 0.5em))
calc(-0.25em - (var(--easeAmount) * 0.25em)) currentColor;
z-index: 1;
} }
} }

View File

@ -9,3 +9,11 @@
@import "word-cloud"; @import "word-cloud";
@import "favorite-tags"; @import "favorite-tags";
@import "favorite-categories"; @import "favorite-categories";
h1,
h2,
h3,
h4,
h5 {
font-family: "Rubik", Helvetica, Arial, sans-serif;
}

View File

@ -1,3 +1,17 @@
:root {
--frame-1: url(/plugins/discourse-rewind/images/bg-frames/bg-2_1.png);
--frame-2: url(/plugins/discourse-rewind/images/bg-frames/bg-2_2.png);
--frame-3: url(/plugins/discourse-rewind/images/bg-frames/bg-2_3.png);
--frame-4: url(/plugins/discourse-rewind/images/bg-frames/bg-2_4.png);
--frame-5: url(/plugins/discourse-rewind/images/bg-frames/bg-2_5.png);
--frame-6: url(/plugins/discourse-rewind/images/bg-frames/bg-2_6.png);
--frame-7: url(/plugins/discourse-rewind/images/bg-frames/bg-2_7.png);
--frame-8: url(/plugins/discourse-rewind/images/bg-frames/bg-2_8.png);
--frame-9: url(/plugins/discourse-rewind/images/bg-frames/bg-2_9.png);
--frame-10: url(/plugins/discourse-rewind/images/bg-frames/bg-2_10.png);
--frame-11: url(/plugins/discourse-rewind/images/bg-frames/bg-2_11.png);
}
.rewind-container { .rewind-container {
border-radius: var(--d-border-radius); border-radius: var(--d-border-radius);
box-sizing: border-box; box-sizing: border-box;
@ -47,12 +61,15 @@
} }
.background-2 { .background-2 {
--frame: var(--frame-1);
position: absolute; position: absolute;
width: 100%; width: 100%;
height: 1000%; height: 1000%;
background: url(/plugins/discourse-rewind/images/bg-2.png); background: var(--frame);
background-size: contain; background-size: contain;
transform: translateY(0px); transform: translateY(0px);
will-change: background;
backface-visibility: hidden;
} }
.rewind__scroll-wrapper { .rewind__scroll-wrapper {

View File

@ -1,5 +1,2 @@
.rewind-report-page.-word-cloud { .rewind-report-page.-word-cloud {
.rewind-report-container {
gap: 1em;
}
} }

BIN
public/images/bg-2-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB