feat(components/tiles): mouse-based effects
This commit is contained in:
parent
7205033ee5
commit
073676b662
4 changed files with 97 additions and 17 deletions
|
|
@ -9,6 +9,11 @@ Metro-like tile. Must be in a group to display correctly.
|
||||||
import { cva } from "class-variance-authority";
|
import { cva } from "class-variance-authority";
|
||||||
import { onDestroy, onMount } from "svelte";
|
import { onDestroy, onMount } from "svelte";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Max angle value.
|
||||||
|
*/
|
||||||
|
const MAX_ANGLE = 10;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base size of a icon in pixels.
|
* Base size of a icon in pixels.
|
||||||
**/
|
**/
|
||||||
|
|
@ -27,22 +32,22 @@ Metro-like tile. Must be in a group to display correctly.
|
||||||
/**
|
/**
|
||||||
* Base size of a page in pixels.
|
* Base size of a page in pixels.
|
||||||
*/
|
*/
|
||||||
const PAGE_BASE_SIZE = 75;
|
const TILE_BASE_SIZE = 75;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page heights.
|
* Page heights.
|
||||||
*/
|
*/
|
||||||
const PAGE_HEIGHTS = {
|
const PAGE_HEIGHTS = {
|
||||||
small: PAGE_BASE_SIZE,
|
small: TILE_BASE_SIZE,
|
||||||
medium: PAGE_BASE_SIZE * 2,
|
medium: TILE_BASE_SIZE * 2,
|
||||||
wide: PAGE_BASE_SIZE * 2,
|
wide: TILE_BASE_SIZE * 2,
|
||||||
large: PAGE_BASE_SIZE * 4,
|
large: TILE_BASE_SIZE * 4,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Max scroll interval delay in milliseconds.
|
* Max scroll interval delay in milliseconds.
|
||||||
*/
|
*/
|
||||||
const SCROLL_INTERVAL_MAX = 10 * 1000;
|
const SCROLL_INTERVAL_MAX = 7.5 * 1000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Min scroll interval delay in milliseconds.
|
* Min scroll interval delay in milliseconds.
|
||||||
|
|
@ -50,9 +55,9 @@ Metro-like tile. Must be in a group to display correctly.
|
||||||
const SCROLL_INTERVAL_MIN = SCROLL_INTERVAL_MAX / 2;
|
const SCROLL_INTERVAL_MIN = SCROLL_INTERVAL_MAX / 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tile element.
|
* Tile pages.
|
||||||
*/
|
*/
|
||||||
let element: HTMLElement;
|
let pages: HTMLElement;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Properties.
|
* Properties.
|
||||||
|
|
@ -94,7 +99,18 @@ Metro-like tile. Must be in a group to display correctly.
|
||||||
* Tile style.
|
* Tile style.
|
||||||
**/
|
**/
|
||||||
const style = cva(
|
const style = cva(
|
||||||
["bg-(--tile-color)", "overflow-y-hidden", "scroll-smooth"],
|
[
|
||||||
|
"relative",
|
||||||
|
"bg-(--tile-color)",
|
||||||
|
"perspective-distant",
|
||||||
|
"active:scale-95",
|
||||||
|
"active:transform-gpu",
|
||||||
|
"active:transform-3d",
|
||||||
|
"active:rotate-x-(--tile-rotate-x)",
|
||||||
|
"active:rotate-y-(--tile-rotate-y)",
|
||||||
|
"duration-75",
|
||||||
|
"ease-in-out",
|
||||||
|
],
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
size: {
|
size: {
|
||||||
|
|
@ -117,6 +133,38 @@ Metro-like tile. Must be in a group to display correctly.
|
||||||
*/
|
*/
|
||||||
const pageHeight = PAGE_HEIGHTS[size];
|
const pageHeight = PAGE_HEIGHTS[size];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mouse position. Used by border (on mouse over).
|
||||||
|
*/
|
||||||
|
let mousePosition = $state({ x: 0, y: 0 });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotation values. Used by transform (on click).
|
||||||
|
*/
|
||||||
|
let rotationValues = $state({ x: 0, y: 0 });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the mouse position-based values.
|
||||||
|
* @param event `mouseover` event.
|
||||||
|
*/
|
||||||
|
function updateDynamicMouseValues(event: MouseEvent) {
|
||||||
|
const element = event.target as HTMLElement;
|
||||||
|
const bounds = element.getBoundingClientRect();
|
||||||
|
|
||||||
|
mousePosition = {
|
||||||
|
x: event.clientX - bounds.left,
|
||||||
|
y: event.clientY - bounds.top,
|
||||||
|
};
|
||||||
|
|
||||||
|
const tileCenterX = bounds.width / 2;
|
||||||
|
const tileCenterY = bounds.height / 2;
|
||||||
|
|
||||||
|
rotationValues.x =
|
||||||
|
-((mousePosition.y - tileCenterY) / tileCenterY) * MAX_ANGLE;
|
||||||
|
rotationValues.y =
|
||||||
|
-((tileCenterX - mousePosition.x) / tileCenterX) * MAX_ANGLE;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Current page number.
|
* Current page number.
|
||||||
*/
|
*/
|
||||||
|
|
@ -147,7 +195,7 @@ Metro-like tile. Must be in a group to display correctly.
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Scroll the pages.
|
// NOTE: Scroll the pages.
|
||||||
element.scrollBy({ top: pageHeight * scrollMultipler });
|
pages.scrollBy({ top: pageHeight * scrollMultipler });
|
||||||
|
|
||||||
// NOTE: Change the orientation after scroll.
|
// NOTE: Change the orientation after scroll.
|
||||||
if (currentPage === pageCount) {
|
if (currentPage === pageCount) {
|
||||||
|
|
@ -163,7 +211,7 @@ Metro-like tile. Must be in a group to display correctly.
|
||||||
let scrollInterval: NodeJS.Timeout;
|
let scrollInterval: NodeJS.Timeout;
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
pageCount = Math.floor(element.scrollHeight / pageHeight);
|
pageCount = Math.floor(pages.scrollHeight / pageHeight);
|
||||||
|
|
||||||
if (size === "small" || pageCount === 1) {
|
if (size === "small" || pageCount === 1) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -186,17 +234,45 @@ Metro-like tile. Must be in a group to display correctly.
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<a
|
||||||
class={style({ size })}
|
class={style({ size })}
|
||||||
style="
|
style="
|
||||||
--tile-icon: url('{icon}');
|
--tile-icon: url('{icon}');
|
||||||
--tile-icon-size: {iconSize}px;
|
--tile-icon-size: {iconSize}px;
|
||||||
--tile-page-height: {pageHeight}px;
|
--tile-page-height: {pageHeight}px;
|
||||||
--tile-name-display: {size !== 'small' ? 'inline' : 'none'};
|
--tile-name-display: {size !== 'small' ? 'inline' : 'none'};
|
||||||
|
--tile-rotate-x: {rotationValues.x}deg;
|
||||||
|
--tile-rotate-y: {rotationValues.y}deg;
|
||||||
grid-row-start: {row};
|
grid-row-start: {row};
|
||||||
grid-column-start: {column};
|
grid-column-start: {column};
|
||||||
"
|
"
|
||||||
bind:this={element}
|
onmousemove={updateDynamicMouseValues}
|
||||||
|
href="#h"
|
||||||
>
|
>
|
||||||
|
<!-- Tile pages -->
|
||||||
|
<div
|
||||||
|
class="w-full h-full overflow-y-hidden scroll-smooth duration-150"
|
||||||
|
bind:this={pages}
|
||||||
|
>
|
||||||
{@render children()}
|
{@render children()}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Tile border -->
|
||||||
|
<div
|
||||||
|
class="
|
||||||
|
absolute top-0
|
||||||
|
w-full h-full
|
||||||
|
border-2 border-white
|
||||||
|
opacity-0 hover:opacity-50 hover:mask-(--mask) active:opacity-100
|
||||||
|
duration-75 ease-in-out
|
||||||
|
"
|
||||||
|
style="
|
||||||
|
--mask: radial-gradient(
|
||||||
|
{TILE_BASE_SIZE}px
|
||||||
|
at {mousePosition.x}px {mousePosition.y}px,
|
||||||
|
black 45%,
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
"
|
||||||
|
></div>
|
||||||
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@ A group of tiles. Places them correctly in a grid.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
/**
|
||||||
|
* Properties.
|
||||||
|
*/
|
||||||
let {
|
let {
|
||||||
title,
|
title,
|
||||||
rows,
|
rows,
|
||||||
|
|
@ -43,7 +46,7 @@ A group of tiles. Places them correctly in a grid.
|
||||||
rows *= 2;
|
rows *= 2;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section>
|
<section role="group">
|
||||||
<!-- Group title -->
|
<!-- Group title -->
|
||||||
{#if title}
|
{#if title}
|
||||||
<h1 class="py-2 select-none">{title}</h1>
|
<h1 class="py-2 select-none">{title}</h1>
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ Image page for a tile. Must be in a tile to display correctly.
|
||||||
style="height: var(--tile-page-height); background-image: url('{image}');"
|
style="height: var(--tile-page-height); background-image: url('{image}');"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="w-[20px] mt-auto aspect-square bg-contain bg-center bg-no-repeat self-end m-2"
|
class="w-5 mt-auto aspect-square bg-contain bg-center bg-no-repeat self-end m-2"
|
||||||
style="background-image: var(--tile-icon);"
|
style="background-image: var(--tile-icon);"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
<TileGroup title="a lot of mikhails" rows={3} columns={3}>
|
<TileGroup title="a lot of mikhails" rows={3} columns={3}>
|
||||||
<Tile size="small" row={5} column={2} icon={iconSynzr}>
|
<Tile size="small" row={5} column={2} icon={iconSynzr}>
|
||||||
<TileIconPage name="mikhail" />
|
<TileIconPage name="mikhail" />
|
||||||
|
<TileTextPage title="Title" subtitle="Subtitle" text="Text" />
|
||||||
</Tile>
|
</Tile>
|
||||||
|
|
||||||
<Tile size="medium" row={3} column={5} icon={iconSynzr}>
|
<Tile size="medium" row={3} column={5} icon={iconSynzr}>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue