Add hover effect on Location
This commit is contained in:
@@ -1,4 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import { spring } from 'svelte/motion'
|
||||
import dayjs from 'dayjs'
|
||||
import { lerp } from '$utils/functions'
|
||||
// Components
|
||||
import Image from '$components/atoms/Image.svelte'
|
||||
import Badge from '$components/atoms/Badge.svelte'
|
||||
@@ -6,18 +10,67 @@
|
||||
export let location: any
|
||||
export let index: number
|
||||
|
||||
const { name, slug, country, last_updated } = location
|
||||
const { settings: { limit_new }}: any = getContext('global')
|
||||
const { name, slug, country, date_updated } = location
|
||||
|
||||
// Location date limit
|
||||
const dateNowOffset = dayjs().subtract(limit_new, 'day')
|
||||
const dateLocationUpdated = dayjs(date_updated)
|
||||
const isNew = dateLocationUpdated.isAfter(dateNowOffset)
|
||||
|
||||
let locationEl: HTMLElement
|
||||
let photoIndex = 0
|
||||
const offset = spring({ x: 0, y: 0 }, {
|
||||
stiffness: 0.075,
|
||||
damping: 0.9
|
||||
})
|
||||
|
||||
// Moving cursor over
|
||||
const handleMouseMove = ({ clientX }: MouseEvent) => {
|
||||
const { width, left } = locationEl.getBoundingClientRect()
|
||||
const moveProgress = (clientX - left) / width // 0 to 1
|
||||
|
||||
// Move horizontally
|
||||
offset.update($c => ({
|
||||
x: lerp(-56, 56, moveProgress),
|
||||
y: 0
|
||||
}))
|
||||
|
||||
// Change photo index from mouse position percentage
|
||||
photoIndex = Math.round(lerp(0, 3, moveProgress))
|
||||
}
|
||||
|
||||
// Leaving mouseover
|
||||
const handleMouseLeave = (event: MouseEvent) => {
|
||||
offset.update($c => ({
|
||||
x: $c.x,
|
||||
y: 40
|
||||
}))
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="location" role="listitem">
|
||||
<a href="/{country.slug}/{slug}" rel="prefetch" sveltekit-noscroll>
|
||||
<div class="location" role="listitem" bind:this={locationEl}
|
||||
style="--offset-x: {$offset.x}px; --offset-y: {$offset.y}px; --rotate: {$offset.x * 0.15}deg"
|
||||
>
|
||||
<a href="/{country.slug}/{slug}"
|
||||
on:mousemove={handleMouseMove}
|
||||
on:mouseleave={handleMouseLeave}
|
||||
sveltekit-noscroll
|
||||
>
|
||||
<Image id={country.flag.id} alt="Flag of {country.name}" width={32} height={32} />
|
||||
<h3 class="location__name">
|
||||
{name}
|
||||
</h3>
|
||||
<span class="text-label location__country">{country.name}</span>
|
||||
{#if index < 2}
|
||||
<dl>
|
||||
<dt class="location__name">
|
||||
{name}
|
||||
</dt>
|
||||
<dd class="location__country text-label">
|
||||
{country.name}
|
||||
</dd>
|
||||
</dl>
|
||||
{#if isNew}
|
||||
<Badge text="New" />
|
||||
{/if}
|
||||
</a>
|
||||
<div class="location__photos">
|
||||
<span>{photoIndex}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,10 +1,42 @@
|
||||
.location {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
|
||||
& > * {
|
||||
a {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 24px;
|
||||
text-decoration: none;
|
||||
|
||||
// Background circle
|
||||
&:after {
|
||||
opacity: 0;
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 256px;
|
||||
height: 256px;
|
||||
transform: translate3d(-50%, -50%, 0) scale(0.8);
|
||||
transform-origin: 50% 50%;
|
||||
background-color: rgba($color-tertiary, 0.1);
|
||||
border-radius: 100%;
|
||||
transition: transform 0.75s var(--ease-quart), opacity 0.75s var(--ease-quart);
|
||||
}
|
||||
|
||||
// Hover
|
||||
&:hover {
|
||||
strong {
|
||||
color: $color-tertiary;
|
||||
}
|
||||
&:after {
|
||||
opacity: 1;
|
||||
transform: translate3d(-50%, -50%, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Flag
|
||||
@@ -13,21 +45,53 @@
|
||||
margin: 0 auto;
|
||||
border-radius: 100%;
|
||||
}
|
||||
// Link
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
// Location name
|
||||
&__name {
|
||||
display: block;
|
||||
color: $color-secondary;
|
||||
margin: 20px 0 -2px;
|
||||
margin: 20px 0 8px;
|
||||
font-size: rem(48px);
|
||||
font-family: $font-serif;
|
||||
font-weight: 300;
|
||||
line-height: 1.2;
|
||||
transition: color 0.75s var(--ease-quart);
|
||||
}
|
||||
|
||||
// Country
|
||||
&__country {
|
||||
color: $color-tertiary;
|
||||
}
|
||||
|
||||
// Badge
|
||||
.badge {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: -32px;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
// Photos
|
||||
&__photos {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: -10%;
|
||||
left: 50%;
|
||||
width: 240px;
|
||||
height: 160px;
|
||||
background: #444;
|
||||
border-radius: 6px;
|
||||
transform: translate3d(calc(-50% + var(--offset-x)), calc(-50% + var(--offset-y)), 0) rotate(var(--rotate));
|
||||
overflow: hidden;
|
||||
box-shadow: 0 8px 8px rgba(#000, 0.1), 0 16px 28px rgba(#000, 0.12);
|
||||
pointer-events: none;
|
||||
transition: opacity 0.5s var(--ease-quart);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.location__photos {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,11 +21,11 @@
|
||||
flex-flow: row wrap;
|
||||
justify-content: center;
|
||||
max-width: 1200px;
|
||||
margin: 128px auto 80px;
|
||||
margin: 104px auto 80px;
|
||||
justify-items: center;
|
||||
|
||||
.location {
|
||||
margin: 0 40px 80px;
|
||||
margin: 0 22px 56px;
|
||||
}
|
||||
}
|
||||
}
|
||||
6
src/utils/functions.ts
Normal file
6
src/utils/functions.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Linear Interpolation
|
||||
*/
|
||||
export const lerp = (start: number, end: number, amt: number): number => {
|
||||
return (1 - amt) * start + amt * end
|
||||
}
|
||||
Reference in New Issue
Block a user