Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add ripple effect #107

Closed
wants to merge 14 commits into from
Closed

feat: add ripple effect #107

wants to merge 14 commits into from

Conversation

not-nullptr
Copy link
Contributor

@not-nullptr not-nullptr commented May 9, 2024

this PR adds an albeit rudimentary ripple effect to most clickable components. there's no clear consensus on how ripple should look in m3 just yet so this currently replicates a version of what's seen on the m3 website, except slightly slower and with a lighter colour to improve accessibility.

@KTibow
Copy link
Owner

KTibow commented May 9, 2024

Thanks for the contribution. However...
In M3, a new ripple was introduced. The main thing that's different is how it's less crisp and more fuzzy. Clip from documentary explaining it Material Web doc page
If you could implement it, that would be very much appreciated.

(Also, ideally, each component would need less modification and events, and could just have a ripple plopped inside of it. Eg the ripple attaches to the parent element, like md-ripple does.)

@not-nullptr
Copy link
Contributor Author

for your first point, yeah 1000% my bad, should've remembered. google's official implementation of m3 on the web doesn't even do this yet so i guess we'd be trailblazers haha

for your second point however there isn't all that much modification outside of what is required. im just exporting a ripple function from the ripple component which handles all the heavy lifting; all other code modifications are to allow an element to call that function (i assume you're looking at the tab code?)

@KTibow
Copy link
Owner

KTibow commented May 9, 2024

doesn't even do this yet

I'm pretty sure material-web already does it though.
Screencast from 2024-05-09 07-11-10.webm

isn't all that much modification outside of what is required

Right, but the modification can still be reduced. Auto-attaching to the parent would simplify the logic and reduce the scope of this PR.

@not-nullptr
Copy link
Contributor Author

i'll see what i can do, but i'm gonna try and improve the visuals first ^^'

@not-nullptr
Copy link
Contributor Author

@KTibow made the effect more accurate using svg noise and a mask. i'll clean up the code next :3

@not-nullptr
Copy link
Contributor Author

code's cleaned up. unfortunately can't send a sample video since the noise gets washed away in compression artifacts, but i think it looks pretty accurate

@not-nullptr
Copy link
Contributor Author

apologies for all the new commits; i've gotten feedback from various android users on how the effect could be improved.

@KTibow
Copy link
Owner

KTibow commented May 11, 2024

I appreciate the work, but I need to make this closer to the original (possibly also reducing complexity, eg having 40 svgs on a page seems bad for perf) before it's merged. Unless you want to take up initiative, I'll try to update it to be closer later today.

@not-nullptr
Copy link
Contributor Author

I appreciate the work, but I need to make this closer to the original (possibly also reducing complexity, eg having 40 svgs on a page seems bad for perf) before it's merged. Unless you want to take up initiative, I'll try to update it to be closer later today.

the original provided is inaccurate to the current state of material you, afaik (though this has only something i've been told, so i wouldn't know.) regarding performance issues, a simple way to fix this would be using #each instead of cloning an already-existing element. i could implement this now if you want?

@KTibow
Copy link
Owner

KTibow commented May 11, 2024

Right, but I'll still need to make this

  • use pure radial gradients, no SVGs
  • remain pressed if your mouse remains pressed
  • not slow down while rippling

Like the Android version does:
https://github.com/KTibow/m3-svelte/assets/10727862/2e727b6e-7002-464d-b0ca-f95ce75d7503

What are the things you had to change in your ripple make it more accurate to Material 3?

@not-nullptr
Copy link
Contributor Author

not-nullptr commented May 11, 2024

the svg provides a white noise effect, which is standard on androids implementation of material you. unless you want to embed a very large image into the library (since the noise needs to be masked, not scaled), an svg is the best way to approach this.

that being said, my implementation of the ripple isn't entirely accurate...

@not-nullptr
Copy link
Contributor Author

image

notice the noise effect on the edges.

@not-nullptr
Copy link
Contributor Author

@KTibow i've fixed the checks and improved the ripple effect some more. i would argue its pretty close to perfect now.

@KTibow
Copy link
Owner

KTibow commented May 11, 2024

Cool. I'm working on a simpler ripple, here's what I have so far.

<script lang="ts">
  import { onMount } from "svelte";

  let rippleEl: HTMLDivElement;
  let rippleContainer: HTMLDivElement;

  const ripple = async (e: MouseEvent) => {
    const bounds = rippleContainer.getBoundingClientRect();
    const initialSize = Math.max(bounds.width, bounds.height) * 0.2;

    const diagonalSize = Math.hypot(bounds.width, bounds.height) * 2;
    const maxSize = diagonalSize * 1.2;

    rippleEl.style.left = `${e.clientX - bounds.left}px`;
    rippleEl.style.top = `${e.clientY - bounds.top}px`;
    rippleEl.style.width = `${maxSize}px`;
    rippleEl.style.height = `${maxSize}px`;
    rippleEl.classList.add("active");
    rippleEl.animate(
      {
        scale: [initialSize / maxSize, 1],
      },
      { easing: "cubic-bezier(0.2, 0, 0, 1)", duration: 450 },
    );

    const stop = () => {
      rippleEl.classList.remove("active");
      window.removeEventListener("mouseup", stop);
    };
    window.addEventListener("mouseup", stop);
  };

  onMount(() => {
    rippleContainer.parentElement?.addEventListener("mousedown", ripple);
    return () => {
      rippleContainer.parentElement?.removeEventListener("mousedown", ripple);
    };
  });
</script>

<div class="rippleContainer" bind:this={rippleContainer}>
  <div
    style="background: radial-gradient(closest-side, currentColor max(100% - 300px, 65%), transparent 100%);"
    bind:this={rippleEl}
    class="ripple"
  />
</div>

<style>
  .rippleContainer {
    position: absolute;
    inset: 0;
    border-radius: inherit;
    overflow: hidden;
  }

  .ripple {
    border-radius: 50%;
    position: absolute;
    pointer-events: none;
    overflow: hidden;

    width: 100%;
    height: 100%;
    translate: -50% -50%;
    opacity: 0;

    transition: opacity 375ms linear;
  }
  .ripple:global(.active) {
    opacity: 0.12;

    transition-duration: 100ms;
  }
</style>

@not-nullptr
Copy link
Contributor Author

this works, but it doesn't have noise and it doesn't handle multiple clicks within a short timespan well

@KTibow
Copy link
Owner

KTibow commented May 11, 2024

i understand that, but i really need to keep bundle sizes down, and i would rather just have something simple that looks like material web. noise is a subtle detail (heck, i'd go as far as to say it's only required on oled screens) and i can easily handle multiple clicks.

aside from complexity, the other thing i worry about is that right now there's two animations, one from css and one from the ripple. ideally the ripple can handle all of the animations, including hover, like material web does it. it would be progressively enhanced, from css to js.

idk. i kinda feel like i might need dependency injection somewhere in this which would further complicate things, and ideally i can implement this relatively simply.

@KTibow
Copy link
Owner

KTibow commented May 11, 2024

Some other problems:

  • handle right click -> inspect (this should cause the ripple to go away)
  • handle parent being disabled

@not-nullptr
Copy link
Contributor Author

right now, material web components does not implement the noise. if you'd rather be ahead of the curve, you could merge my fix. but honestly, your one looks fine too.

@not-nullptr
Copy link
Contributor Author

anything on this? it doesnt necessarily need to be my ripple but i think this is seriously holding back the project, the ripple on click is a huge part of material's design language

@KTibow
Copy link
Owner

KTibow commented Nov 1, 2024

yeah i honestly don't know why i haven't been working on this. i'm going to see if i can do this (in the subjectively most "right" way) by making a single component that handles hover as well as click states.

@KTibow
Copy link
Owner

KTibow commented Nov 3, 2024

@not-nullptr
Copy link
Contributor Author

@not-nullptr https://github.com/KTibow/m3-svelte/tree/ripples how does this look

feels too slow at the beginning (+ is the hero supposed to have a ripple? lol)

@KTibow
Copy link
Owner

KTibow commented Nov 3, 2024

yeah

just upped the speed to make it feel more functional

@not-nullptr
Copy link
Contributor Author

seems great now

@KTibow KTibow mentioned this pull request Nov 4, 2024
@KTibow KTibow closed this in #129 Nov 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants