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
2 changes: 2 additions & 0 deletions src/lib/buttons/Button.svelte
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script lang="ts">
import Ripple from "$lib/effects/Ripple.svelte";
import type { HTMLButtonAttributes } from "svelte/elements";
export let display = "inline-flex";
export let extraOptions: HTMLButtonAttributes = {};
Expand All @@ -14,6 +15,7 @@
style="display: {display};"
{...extraOptions}
>
<Ripple color="secondary" />
<div class="layer" />
<slot />
</button>
Expand Down
4 changes: 4 additions & 0 deletions src/lib/buttons/FAB.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import type { HTMLButtonAttributes } from "svelte/elements";
import Icon from "$lib/misc/_icon.svelte";
import type { IconifyIcon } from "@iconify/types";
import { createEventDispatcher } from "svelte";
import Ripple from "$lib/effects/Ripple.svelte";

export let display = "inline-flex";
export let extraOptions: HTMLButtonAttributes = {};
Expand All @@ -10,6 +12,7 @@
export let elevation: "normal" | "lowered" | "none" = "normal";
export let icon: IconifyIcon | undefined = undefined;
export let text: string | undefined = undefined;

$: {
if (!icon && !text) console.warn("you need at least something in a FAB");
if (size != "normal" && text) console.warn("extended fabs are supposed to use size normal");
Expand All @@ -22,6 +25,7 @@
style="display: {display};"
{...extraOptions}
>
<Ripple color="secondary" />
<div class="layer" />
{#if icon}
<Icon {icon} />
Expand Down
28 changes: 27 additions & 1 deletion src/lib/buttons/SegmentedButtonItem.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,46 @@
import Icon from "$lib/misc/_icon.svelte";
import type { IconifyIcon } from "@iconify/types";
import iconCheck from "@ktibow/iconset-material-symbols/check";
import Ripple from "$lib/effects/Ripple.svelte";
import { onMount } from "svelte";

export let display = "flex";
export let extraOptions: HTMLLabelAttributes = {};
export let input: string;
export let icon: IconifyIcon | undefined = undefined;
let disabled = false;

onMount(() => {
const inputEl = document.getElementById(input) as HTMLInputElement;
if (!inputEl) return;
disabled = inputEl.disabled;
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
// Check the modified attributeName is "disabled"
if (mutation.attributeName === "disabled") {
alert("disabled changed");
}
});
});
var config = { attributes: true };
observer.observe(inputEl, config);
return () => {
observer.disconnect();
};
});
</script>

<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
<label
class:custom-icon={icon}
for={input}
class="m3-font-label-large"
style="display: {display};"
style="display: {display}; overflow: hidden;"
{...extraOptions}
>
{#if !disabled}
<Ripple color="secondary" />
{/if}
<div class="layer" />
<div class="pad" />
{#if icon}
Expand Down
4 changes: 3 additions & 1 deletion src/lib/containers/CardClickable.svelte
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script lang="ts">
import Ripple from "$lib/effects/Ripple.svelte";
import type { HTMLAttributes, HTMLButtonAttributes } from "svelte/elements";

export let display = "flex";
Expand All @@ -9,9 +10,10 @@
<button
on:click|stopPropagation
class="m3-container type-{type}"
style="display: {display};"
style="display: {display}; overflow: hidden;"
{...extraOptions}
>
<Ripple color="secondary" />
<div class="layer" />
<slot />
</button>
Expand Down
2 changes: 1 addition & 1 deletion src/lib/containers/Dialog.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
}
}}
bind:this={dialog}
style="display: {display};"
style="display: {display}; z-index: 9999;"
{...extraOptions}
>
<div class="m3-container">
Expand Down
4 changes: 4 additions & 0 deletions src/lib/containers/MenuItem.svelte
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script lang="ts">
import Ripple from "$lib/effects/Ripple.svelte";
import Icon from "$lib/misc/_icon.svelte";
import type { IconifyIcon } from "@iconify/types";

Expand All @@ -7,6 +8,7 @@
</script>

<button class="item m3-font-label-large" {disabled} on:click>
<Ripple color="secondary" />
{#if icon == "space"}
<span class="icon" />
{:else if icon}
Expand All @@ -19,6 +21,8 @@

<style>
.item {
position: relative;
overflow: hidden;
display: flex;
align-items: center;
height: 3rem;
Expand Down
231 changes: 231 additions & 0 deletions src/lib/effects/Ripple.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
<script lang="ts">
import { onMount } from "svelte";

let rippleEl: HTMLDivElement;
let rippleContainer: HTMLDivElement;
export let color: "primary" | "surface" | "secondary" | "tertiary" = "primary";

const ripple = async (e: MouseEvent) => {
const clone = rippleEl.cloneNode(true) as HTMLDivElement;
rippleEl.parentElement!.appendChild(clone);
const hover = () => {
clone.style.display = "";
};
const leave = () => {
clone.style.display = "none";
};
const svg = clone.querySelector("svg")!;
const bounds = rippleEl.getBoundingClientRect();
if (bounds.width > bounds.height) {
clone.style.height = "100%";
clone.style.width = clone.offsetHeight + "px";
} else {
clone.style.width = "100%";
clone.style.height = clone.offsetWidth + "px";
}
svg.style.width = clone.offsetWidth * 2 + "px";
svg.style.height = clone.offsetHeight * 2 + "px";
clone.style.transform = `translate(${e.clientX - bounds.left}px, ${e.clientY - bounds.top}px)`;
clone.style.opacity = "1";
const duration = 2000;
const apparentDuration = duration / 4;
clone.animate(
[
{
opacity: 1,
},
{
opacity: 0,
},
],
{
duration: apparentDuration,
easing: "ease-out",
fill: "forwards",
delay: apparentDuration / 2,
},
);

svg.animate(
[
{
opacity: 0.75,
},
{
opacity: 0,
},
],
{
duration: apparentDuration,
easing: "ease-out",
fill: "forwards",
},
);

clone.parentElement?.addEventListener("mouseenter", hover);
clone.parentElement?.addEventListener("mouseleave", leave);
clone.parentElement?.parentElement?.addEventListener("mouseenter", hover);
clone.parentElement?.parentElement?.addEventListener("mouseleave", leave);
await clone.animate(
[
{
width: 0,
height: 0,
},
{
width: clone.offsetWidth * 6 + "px",
height: clone.offsetHeight * 6 + "px",
},
],
{
duration: duration,
easing: "cubic-bezier(.05,.7,.1,1)",
fill: "forwards",
},
).finished;
clone.remove();
};

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

<div class="rippleContainer" bind:this={rippleContainer}>
<div
style="--col: rgb(var(--m3-scheme-on-{color}-container))"
bind:this={rippleEl}
class="ripple color-{color}"
>
<div class="maskable-noise">
<svg
class="rippleSvg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 700 700"
width="700"
height="700"
style="--col: rgb(var(--m3-scheme-{color}));width: 700px; height: 700px; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);"
><defs
><filter
id="noise"
x="0"
y="0"
width="700"
height="700"
filterUnits="objectBoundingBox"
primitiveUnits="userSpaceOnUse"
color-interpolation-filters="sRGB"
>
<feTurbulence
type="fractalNoise"
baseFrequency="1.4"
numOctaves="2"
seed="2"
stitchTiles="stitch"
x="0"
y="0"
width="700"
height="700"
result="turbulence"
></feTurbulence>
<feColorMatrix
type="saturate"
values="0"
x="0%"
y="0%"
width="100%"
height="100%"
in="turbulence"
result="colormatrix"
></feColorMatrix>
<feComponentTransfer
x="0%"
y="0%"
width="100%"
height="100%"
in="colormatrix"
result="componentTransfer"
>
<feFuncR type="linear" slope="3"></feFuncR>
<feFuncG type="linear" slope="3"></feFuncG>
<feFuncB type="linear" slope="3"></feFuncB>
</feComponentTransfer>
<feColorMatrix
x="0%"
y="0%"
width="100%"
height="100%"
in="componentTransfer"
result="colormatrix2"
type="matrix"
values="1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
0 0 0 18 -10"
></feColorMatrix>
</filter></defs
><g
><rect width="700" height="700" fill="black"></rect><rect
width="700"
height="700"
fill="black"
filter="url(#noise)"
opacity="1"
></rect></g
></svg
>
</div>
</div>
</div>

<style>
.rippleContainer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}

.ripple {
background: radial-gradient(
circle,
color-mix(in srgb, var(--col), transparent 90%) 20%,
transparent 40%
);
border-radius: 50%;
position: absolute;
pointer-events: none;
overflow: hidden;
width: 0;
height: 0;
display: flex;
justify-content: center;
align-items: center;
mask-image: radial-gradient(circle, black 0%, transparent 50%);
mask-size: 100% 100%;
}

.maskable-noise {
mask-image: radial-gradient(circle, transparent 10%, black 100%);
-webkit-mask-image: radial-gradient(circle, transparent 10%, black 100%);
mask-size: 100% 100%;
-webkit-mask-size: 100% 100%;
width: 100%;
height: 100%;
}

.rippleSvg {
fill: var(--col);
opacity: 0.75;
}
</style>
4 changes: 3 additions & 1 deletion src/lib/forms/Chip.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import type { HTMLButtonAttributes } from "svelte/elements";
import Icon from "$lib/misc/_icon.svelte";
import type { IconifyIcon } from "@iconify/types";
import Ripple from "$lib/effects/Ripple.svelte";

export let display = "inline-flex";
export let extraOptions: HTMLButtonAttributes = {};
Expand All @@ -24,13 +25,14 @@

<button
class="m3-container type-{type}"
style="display: {display}"
style="display: {display}; overflow: hidden;"
class:elevated
class:selected
{disabled}
on:click
{...extraOptions}
>
<Ripple color="secondary" />
<div class="layer" />
{#if icon}
<Icon {icon} class="leading" />
Expand Down
Loading
Loading