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">
|
||||
import Lightbox from './Lightbox.svelte'
|
||||
import TiltCard from './TiltCard.svelte'
|
||||
|
||||
let {
|
||||
images = [],
|
||||
|
|
@ -46,15 +47,19 @@
|
|||
|
||||
{#if images.length === 1}
|
||||
<!-- Single image -->
|
||||
<button class="single-image image-button" onclick={() => openLightbox()}>
|
||||
<img src={images[0]} {alt} />
|
||||
</button>
|
||||
<TiltCard>
|
||||
<button class="single-image image-button" onclick={() => openLightbox()}>
|
||||
<img src={images[0]} {alt} />
|
||||
</button>
|
||||
</TiltCard>
|
||||
{:else if images.length > 1}
|
||||
<!-- Slideshow -->
|
||||
<div class="slideshow">
|
||||
<button class="main-image image-button" onclick={() => openLightbox()}>
|
||||
<img src={images[selectedIndex]} alt="{alt} {selectedIndex + 1}" />
|
||||
</button>
|
||||
<TiltCard>
|
||||
<button class="main-image image-button" onclick={() => openLightbox()}>
|
||||
<img src={images[selectedIndex]} alt="{alt} {selectedIndex + 1}" />
|
||||
</button>
|
||||
</TiltCard>
|
||||
<div class="thumbnails">
|
||||
{#each Array(totalSlots) as _, index}
|
||||
{#if index < images.length}
|
||||
|
|
@ -84,11 +89,6 @@
|
|||
cursor: pointer;
|
||||
display: block;
|
||||
width: 100%;
|
||||
transition: transform 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: 2px solid $red-60;
|
||||
|
|
@ -101,6 +101,10 @@
|
|||
aspect-ratio: 4 / 3;
|
||||
border-radius: $image-corner-radius;
|
||||
overflow: hidden;
|
||||
// Force GPU acceleration and proper clipping
|
||||
transform: translateZ(0);
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
|
|
|
|||
|
|
@ -16,9 +16,46 @@
|
|||
new RegExp(`(${name})`, 'gi'),
|
||||
`<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>
|
||||
|
||||
<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">
|
||||
<SVGHoverEffect
|
||||
{SVGComponent}
|
||||
|
|
@ -42,6 +79,16 @@
|
|||
padding: $unit-3x;
|
||||
background: $grey-100;
|
||||
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 {
|
||||
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