Add SVGHoverEffect component
This component lets you overlay an SVG component over a background color and get mouse interactivity on hover.
This commit is contained in:
parent
78abc3d339
commit
3580b5d3da
1 changed files with 92 additions and 0 deletions
92
src/lib/components/SVGHoverEffect.svelte
Normal file
92
src/lib/components/SVGHoverEffect.svelte
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
<script>
|
||||
import { onMount } from 'svelte'
|
||||
import { spring } from 'svelte/motion'
|
||||
|
||||
export let SVGComponent
|
||||
export let backgroundColor = '#f0f0f0'
|
||||
export let maxMovement = 20
|
||||
export let smoothness = 0.1
|
||||
export let containerHeight = '300px'
|
||||
export let bounceStiffness = 0.1
|
||||
export let bounceDamping = 0.4
|
||||
|
||||
let container
|
||||
let svg
|
||||
let isHovering = false
|
||||
let animationFrame
|
||||
|
||||
const position = spring(
|
||||
{ x: 0, y: 0 },
|
||||
{
|
||||
stiffness: bounceStiffness,
|
||||
damping: bounceDamping
|
||||
}
|
||||
)
|
||||
|
||||
$: if (svg) {
|
||||
svg.style.transform = `translate(calc(-50% + ${$position.x}px), calc(-50% + ${$position.y}px))`
|
||||
}
|
||||
|
||||
function handleMouseMove(event) {
|
||||
isHovering = true
|
||||
const rect = container.getBoundingClientRect()
|
||||
const x = event.clientX - rect.left
|
||||
const y = event.clientY - rect.top
|
||||
|
||||
const targetX = (x / rect.width - 0.5) * 2 * maxMovement
|
||||
const targetY = (y / rect.height - 0.5) * 2 * maxMovement
|
||||
|
||||
position.set({ x: targetX, y: targetY }, { hard: false })
|
||||
}
|
||||
|
||||
function handleMouseLeave() {
|
||||
isHovering = false
|
||||
position.set({ x: 0, y: 0 }, { hard: false })
|
||||
}
|
||||
|
||||
function updateSVGPosition() {
|
||||
if (isHovering) {
|
||||
const currentX = $position.x
|
||||
const currentY = $position.y
|
||||
position.update((pos) => ({
|
||||
x: pos.x + (currentX - pos.x) * smoothness,
|
||||
y: pos.y + (currentY - pos.y) * smoothness
|
||||
}))
|
||||
}
|
||||
|
||||
animationFrame = requestAnimationFrame(updateSVGPosition)
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
svg = container.querySelector('svg')
|
||||
if (svg) {
|
||||
svg.style.position = 'absolute'
|
||||
svg.style.left = '50%'
|
||||
svg.style.top = '50%'
|
||||
svg.style.transform = 'translate(-50%, -50%)'
|
||||
}
|
||||
|
||||
updateSVGPosition()
|
||||
|
||||
return () => {
|
||||
cancelAnimationFrame(animationFrame)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={container}
|
||||
on:mousemove={handleMouseMove}
|
||||
on:mouseleave={handleMouseLeave}
|
||||
style="position: relative; overflow: hidden; background-color: {backgroundColor}; height: {containerHeight}; display: flex; justify-content: center; align-items: center;"
|
||||
>
|
||||
<div style="position: relative; width: 100%; height: 100%;">
|
||||
<svelte:component this={SVGComponent} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
div {
|
||||
border-radius: $corner-radius;
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in a new issue