TiltCard
This commit is contained in:
parent
6ebe2d59dc
commit
4d8e23ae5b
3 changed files with 127 additions and 12 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Lightbox from './Lightbox.svelte'
|
import Lightbox from './Lightbox.svelte'
|
||||||
|
import TiltCard from './TiltCard.svelte'
|
||||||
|
|
||||||
let {
|
let {
|
||||||
images = [],
|
images = [],
|
||||||
|
|
@ -46,15 +47,19 @@
|
||||||
|
|
||||||
{#if images.length === 1}
|
{#if images.length === 1}
|
||||||
<!-- Single image -->
|
<!-- Single image -->
|
||||||
|
<TiltCard>
|
||||||
<button class="single-image image-button" onclick={() => openLightbox()}>
|
<button class="single-image image-button" onclick={() => openLightbox()}>
|
||||||
<img src={images[0]} {alt} />
|
<img src={images[0]} {alt} />
|
||||||
</button>
|
</button>
|
||||||
|
</TiltCard>
|
||||||
{:else if images.length > 1}
|
{:else if images.length > 1}
|
||||||
<!-- Slideshow -->
|
<!-- Slideshow -->
|
||||||
<div class="slideshow">
|
<div class="slideshow">
|
||||||
|
<TiltCard>
|
||||||
<button class="main-image image-button" onclick={() => openLightbox()}>
|
<button class="main-image image-button" onclick={() => openLightbox()}>
|
||||||
<img src={images[selectedIndex]} alt="{alt} {selectedIndex + 1}" />
|
<img src={images[selectedIndex]} alt="{alt} {selectedIndex + 1}" />
|
||||||
</button>
|
</button>
|
||||||
|
</TiltCard>
|
||||||
<div class="thumbnails">
|
<div class="thumbnails">
|
||||||
{#each Array(totalSlots) as _, index}
|
{#each Array(totalSlots) as _, index}
|
||||||
{#if index < images.length}
|
{#if index < images.length}
|
||||||
|
|
@ -84,11 +89,6 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
transition: transform 0.2s ease;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
transform: scale(0.98);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: 2px solid $red-60;
|
outline: 2px solid $red-60;
|
||||||
|
|
@ -101,6 +101,10 @@
|
||||||
aspect-ratio: 4 / 3;
|
aspect-ratio: 4 / 3;
|
||||||
border-radius: $image-corner-radius;
|
border-radius: $image-corner-radius;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
// Force GPU acceleration and proper clipping
|
||||||
|
transform: translateZ(0);
|
||||||
|
-webkit-backface-visibility: hidden;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,46 @@
|
||||||
new RegExp(`(${name})`, 'gi'),
|
new RegExp(`(${name})`, 'gi'),
|
||||||
`<span style="color: ${highlightColor};">$1</span>`
|
`<span style="color: ${highlightColor};">$1</span>`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 3D tilt effect
|
||||||
|
let cardElement: HTMLDivElement
|
||||||
|
let isHovering = false
|
||||||
|
let transform = ''
|
||||||
|
|
||||||
|
function handleMouseMove(e: MouseEvent) {
|
||||||
|
if (!cardElement || !isHovering) return
|
||||||
|
|
||||||
|
const rect = cardElement.getBoundingClientRect()
|
||||||
|
const x = e.clientX - rect.left
|
||||||
|
const y = e.clientY - rect.top
|
||||||
|
|
||||||
|
const centerX = rect.width / 2
|
||||||
|
const centerY = rect.height / 2
|
||||||
|
|
||||||
|
const rotateX = ((y - centerY) / centerY) * -4 // -4 to 4 degrees
|
||||||
|
const rotateY = ((x - centerX) / centerX) * 4 // -4 to 4 degrees
|
||||||
|
|
||||||
|
transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) scale3d(1.014, 1.014, 1.014)`
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseEnter() {
|
||||||
|
isHovering = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseLeave() {
|
||||||
|
isHovering = false
|
||||||
|
transform = 'perspective(1000px) rotateX(0) rotateY(0) scale3d(1, 1, 1)'
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="project-item {isEven ? 'even' : 'odd'}">
|
<div
|
||||||
|
class="project-item {isEven ? 'even' : 'odd'}"
|
||||||
|
bind:this={cardElement}
|
||||||
|
on:mousemove={handleMouseMove}
|
||||||
|
on:mouseenter={handleMouseEnter}
|
||||||
|
on:mouseleave={handleMouseLeave}
|
||||||
|
style="transform: {transform};"
|
||||||
|
>
|
||||||
<div class="project-logo">
|
<div class="project-logo">
|
||||||
<SVGHoverEffect
|
<SVGHoverEffect
|
||||||
{SVGComponent}
|
{SVGComponent}
|
||||||
|
|
@ -42,6 +79,16 @@
|
||||||
padding: $unit-3x;
|
padding: $unit-3x;
|
||||||
background: $grey-100;
|
background: $grey-100;
|
||||||
border-radius: $card-corner-radius;
|
border-radius: $card-corner-radius;
|
||||||
|
transition: transform 0.15s ease-out, box-shadow 0.15s ease-out;
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
will-change: transform;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow:
|
||||||
|
0 10px 30px rgba(0, 0, 0, 0.1),
|
||||||
|
0 1px 8px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
&.odd {
|
&.odd {
|
||||||
flex-direction: row-reverse;
|
flex-direction: row-reverse;
|
||||||
|
|
|
||||||
64
src/lib/components/TiltCard.svelte
Normal file
64
src/lib/components/TiltCard.svelte
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
<script lang="ts">
|
||||||
|
let cardElement: HTMLDivElement
|
||||||
|
let isHovering = false
|
||||||
|
let transform = ''
|
||||||
|
|
||||||
|
function handleMouseMove(e: MouseEvent) {
|
||||||
|
if (!cardElement || !isHovering) return
|
||||||
|
|
||||||
|
const rect = cardElement.getBoundingClientRect()
|
||||||
|
const x = e.clientX - rect.left
|
||||||
|
const y = e.clientY - rect.top
|
||||||
|
|
||||||
|
const centerX = rect.width / 2
|
||||||
|
const centerY = rect.height / 2
|
||||||
|
|
||||||
|
const rotateX = ((y - centerY) / centerY) * -5 // -4 to 4 degrees
|
||||||
|
const rotateY = ((x - centerX) / centerX) * 5 // -4 to 4 degrees
|
||||||
|
|
||||||
|
transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) scale3d(1.014, 1.014, 1.014)`
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseEnter() {
|
||||||
|
isHovering = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseLeave() {
|
||||||
|
isHovering = false
|
||||||
|
transform = 'perspective(1000px) rotateX(0) rotateY(0) scale3d(1, 1, 1)'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="tilt-card"
|
||||||
|
bind:this={cardElement}
|
||||||
|
on:mousemove={handleMouseMove}
|
||||||
|
on:mouseenter={handleMouseEnter}
|
||||||
|
on:mouseleave={handleMouseLeave}
|
||||||
|
style="transform: {transform};"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.tilt-card {
|
||||||
|
transition:
|
||||||
|
transform 0.15s ease-out,
|
||||||
|
box-shadow 0.15s ease-out;
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
will-change: transform;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: $image-corner-radius;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
// Use mask as a fallback for better clipping
|
||||||
|
-webkit-mask-image: -webkit-radial-gradient(white, black);
|
||||||
|
mask-image: radial-gradient(white, black);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow:
|
||||||
|
0 6px 20px rgba(0, 0, 0, 0.1),
|
||||||
|
0 1px 8px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in a new issue