From 73f7ddca3680950beb2619541cbe17afadba4ad3 Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Thu, 22 Aug 2024 18:24:43 -0500 Subject: [PATCH] Animate when boosting (puffing up) --- app/assets/stylesheets/animation.css | 36 ++++++++--------- app/assets/stylesheets/splats.css | 15 +++++-- .../controllers/animation_controller.js | 17 ++++++++ .../controllers/toggle_class_controller.js | 9 +++++ app/javascript/helpers/timing_helpers.js | 39 +++++++++++++++++++ app/views/boosts/_boost.html.erb | 2 +- app/views/splats/_splat.html.erb | 5 ++- config/importmap.rb | 2 + 8 files changed, 101 insertions(+), 24 deletions(-) create mode 100644 app/javascript/controllers/animation_controller.js create mode 100644 app/javascript/controllers/toggle_class_controller.js create mode 100644 app/javascript/helpers/timing_helpers.js diff --git a/app/assets/stylesheets/animation.css b/app/assets/stylesheets/animation.css index 2f39af5b9..986c73c6a 100644 --- a/app/assets/stylesheets/animation.css +++ b/app/assets/stylesheets/animation.css @@ -4,6 +4,24 @@ } /* Keyframes */ +@keyframes bubble-up { + 0% { transform: translate(0, 0); } + 25% { transform: translate(0.5em, -2em); } + 50% { transform: translate(0, -4em); opacity: 0.66; } + 75% { transform: translate(0.5em, -6em); opacity: 0.33; } + 100% { transform: translate(0, -8em); opacity: 0; } +} + +@keyframes float-up-left { + 0% { transform: translate(10px, 20px); } + 100% { transform: translate(0, 0); } +} + +@keyframes float-down-right { + 0% { transform: translate(-10px, -20px); } + 100% { transform: translate(0, 0); } +} + @keyframes shake { 0% { transform: translateX(-2rem); } 25% { transform: translateX(2rem); } @@ -17,21 +35,3 @@ 50% { transform: translateX(-0.2rem); } 75% { transform: translateX(0.2rem); } } - -@keyframes splat { - 0% { transform: scale(150%); opacity: 0; } - 29% { transform: scale(150%); opacity: 0; } - 30% { transform: scale(150%); opacity: 0.5; } - 80% { transform: scale(90%); opacity: 1; } - 100% { transform: scale(100%); } -} - -@keyframes float-up-left { - 0% { transform: translate(10px, 20px); } - 100% { transform: translate(0, 0); } -} - -@keyframes float-down-right { - 0% { transform: translate(-10px, -20px); } - 100% { transform: translate(0, 0); } -} diff --git a/app/assets/stylesheets/splats.css b/app/assets/stylesheets/splats.css index 89359fd13..2fe1d9172 100644 --- a/app/assets/stylesheets/splats.css +++ b/app/assets/stylesheets/splats.css @@ -76,17 +76,26 @@ color: var(--splat-color); } - &.splat__boosts { + &:is(.splat__boosts) { aspect-ratio: 1; - inset: 50cqi -1cqi auto auto; + display: grid; + min-inline-size: 6ch; + padding: 0; + place-items: center; + inset: 55cqi -2cqi auto auto; a { + grid-area: 1/1; text-decoration: none; } .windshield .splat:hover & { transform: translate(0, 1rem) !important; } + + &.boosting { + animation: bubble-up 500ms normal forwards ease-out; + } } &.splat__category { @@ -110,7 +119,7 @@ inset: 2cqi 2cqi auto auto; } - &.splat__meta { + &:where(.splat__meta) { background-color: var(--color-bg); block-size: auto; color: var(--splat-color); diff --git a/app/javascript/controllers/animation_controller.js b/app/javascript/controllers/animation_controller.js new file mode 100644 index 000000000..a6fe306a2 --- /dev/null +++ b/app/javascript/controllers/animation_controller.js @@ -0,0 +1,17 @@ +import { Controller } from "@hotwired/stimulus" +import { nextFrame } from "helpers/timing_helpers" + +export default class extends Controller { + static classes = [ "play" ] + + async play() { + await nextFrame() + this.element.classList.remove(this.playClass) + this.#forceReflow() + this.element.classList.add(this.playClass) + } + + #forceReflow() { + this.element.offsetWidth + } +} diff --git a/app/javascript/controllers/toggle_class_controller.js b/app/javascript/controllers/toggle_class_controller.js new file mode 100644 index 000000000..694106ea2 --- /dev/null +++ b/app/javascript/controllers/toggle_class_controller.js @@ -0,0 +1,9 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static classes = [ "toggle" ] + + toggle() { + this.element.classList.toggle(this.toggleClass) + } +} diff --git a/app/javascript/helpers/timing_helpers.js b/app/javascript/helpers/timing_helpers.js new file mode 100644 index 000000000..f83e6719c --- /dev/null +++ b/app/javascript/helpers/timing_helpers.js @@ -0,0 +1,39 @@ +export function throttle(fn, delay = 1000) { + let timeoutId = null + + return (...args) => { + if (!timeoutId) { + fn(...args) + timeoutId = setTimeout(() => timeoutId = null, delay) + } + } +} + +export function debounce(fn, delay = 1000) { + let timeoutId = null + + return (...args) => { + clearTimeout(timeoutId) + timeoutId = setTimeout(() => fn.apply(this, args), delay) + } +} + +export function nextEventLoopTick() { + return delay(0) +} + +export function onNextEventLoopTick(callback) { + setTimeout(callback, 0) +} + +export function nextFrame() { + return new Promise(requestAnimationFrame) +} + +export function nextEventNamed(eventName, element = window) { + return new Promise(resolve => element.addEventListener(eventName, resolve, { once: true })) +} + +export function delay(ms) { + return new Promise(resolve => setTimeout(resolve, ms)) +} diff --git a/app/views/boosts/_boost.html.erb b/app/views/boosts/_boost.html.erb index aef492e56..4fac9ef2c 100644 --- a/app/views/boosts/_boost.html.erb +++ b/app/views/boosts/_boost.html.erb @@ -1 +1 @@ -<%= link_to splat.boosts.any? ? "+ #{splat.boosts.size}" : "+", new_splat_boost_path(splat) %> +<%= link_to splat.boosts.any? ? "+ #{splat.boosts.size}" : "+", new_splat_boost_path(splat), data: { action: "toggle-class#toggle" } %> diff --git a/app/views/splats/_splat.html.erb b/app/views/splats/_splat.html.erb index b2dc16fc2..101cee03f 100644 --- a/app/views/splats/_splat.html.erb +++ b/app/views/splats/_splat.html.erb @@ -22,7 +22,8 @@ <% end %> -
+ <%= tag.div class: "splat__bubble splat__meta splat__boosts", + data: { controller: "animation toggle-class", animation_play_class: "boosting", toggle_class_toggle_class: "boosting", action: "animationend->toggle-class#toggle" } do %> " src="<%= splat_boosts_path(splat) %>" /> -
+ <% end %> diff --git a/config/importmap.rb b/config/importmap.rb index 909dfc542..768403738 100644 --- a/config/importmap.rb +++ b/config/importmap.rb @@ -4,4 +4,6 @@ pin "application" pin "@hotwired/turbo-rails", to: "turbo.min.js" pin "@hotwired/stimulus", to: "stimulus.min.js" pin "@hotwired/stimulus-loading", to: "stimulus-loading.js" + pin_all_from "app/javascript/controllers", under: "controllers" +pin_all_from "app/javascript/helpers", under: "helpers"