3D卡片悬浮效果
vue
<!-- 3DHover.vue -->
<template>
<div class="card-wrap">
<div class="card-3d">
<img src="https://fun.youth.cn/gnzx/202011/W020201119307688300465.jpg" alt="" />
</div>
</div>
</template>
<script setup lang="tsx">
import { onMounted } from 'vue'
onMounted(() => {
const card3d = document.querySelector('.card-3d') as HTMLDivElement
const yRange = [-20, 20]
const xRange = [-15, 15]
const effectHandle = (e) => {
const { clientX, clientY } = e
const { left, top, width, height } = card3d.getBoundingClientRect()
// 相对卡片的实际移动距离
const x = clientX - left
const y = clientY - top
// 计算比例
const yPercent = y / height
const xPercent = x / width
// 等比运算计算角度
const yDeg = yRange[0] + (yRange[1] - yRange[0]) * yPercent
const xDeg = xRange[0] + (xRange[1] - xRange[0]) * xPercent
// 设置css变量
card3d.style.setProperty('--ry', `${xDeg}deg`)
card3d.style.setProperty('--rx', `${-yDeg}deg`)
card3d.style.setProperty('--per', `${xDeg}%`)
}
card3d.addEventListener('mousemove', effectHandle)
card3d.addEventListener('mouseleave', () => {
card3d.style.setProperty('--ry', `0deg`)
card3d.style.setProperty('--rx', `0deg`)
})
})
</script>
<style lang="scss">
.card-wrap {
display: flex;
justify-content: center;
align-items: center;
position: relative;
overflow: hidden;
background-color: transparent;
padding: 12px 0;
&:before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: -1;
background: url('https://fun.youth.cn/gnzx/202011/W020201119307688300465.jpg');
background-size: cover;
filter: blur(50px);
transform: scale(2);
}
.card-3d {
position: relative;
width: 200px;
border-radius: 10px;
background: #fff;
// 核心样式
transform-style: preserve-3d;
transition: all 0.5s ease;
transform: perspective(500px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));
&:hover {
box-shadow: -3px 3px 10px rgba(0, 0, 0, 0.5);
}
&:hover::before {
display: block;
}
// 添加反光效果
&::before {
content: '';
display: none;
position: absolute;
border-radius: inherit;
inset: 0;
background: linear-gradient(
115deg,
transparent 0%,
rgba(255, 255, 255, 0.5) var(--per, 30%),
rgba(0, 0, 0, 0.5) calc(var(--per, 55%) + 25%),
rgba(255, 255, 255, 0.5) calc(var(--per, 80%) + 50%),
transparent 100%
);
mix-blend-mode: color-dodge;
}
img {
border-radius: inherit;
width: 100%;
}
}
}
</style>
封装成组件
点击查看代码
tsx
// Card3d.tsx
import { defineComponent, onMounted, ref, PropType, CSSProperties } from 'vue'
const style: CSSProperties = {
width: '200px',
borderRadius: '10px',
background: '#fff',
transformStyle: 'preserve-3d',
transition: 'all 0.5s ease',
overflow: 'hidden',
transform: 'perspective(500px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg))',
}
// 验证范围
const validatorFn = (val: Array<number>) => {
return val.length === 2 && val[0] > -90 && val[1] < 90
}
export const Card3D = defineComponent({
name: 'Card3D',
props: {
yRange: {
type: Array as PropType<Array<number>>,
validator: validatorFn,
default: () => [-20, 20],
},
xRange: {
type: Array as PropType<Array<number>>,
validator: validatorFn,
default: () => [-15, 15],
},
},
setup(props, { slots }) {
const cardRef = ref<HTMLDivElement | null>(null)
onMounted(() => {
const card3d = cardRef.value!
const { yRange, xRange } = props
const effectHandle = (e: MouseEvent) => {
const { clientX, clientY } = e
const { left, top, width, height } = card3d.getBoundingClientRect()
// 相对卡片的实际移动距离
const x = clientX - left
const y = clientY - top
// 计算比例
const yPercent = y / height
const xPercent = x / width
// 等比运算计算角度
const yDeg = yRange[0] + (yRange[1] - yRange[0]) * yPercent
const xDeg = xRange[0] + (xRange[1] - xRange[0]) * xPercent
// 设置css变量
card3d.style.setProperty('--ry', `${xDeg}deg`)
card3d.style.setProperty('--rx', `${-yDeg}deg`)
}
card3d.addEventListener('mousemove', effectHandle)
card3d.addEventListener('mouseleave', () => {
card3d.style.setProperty('--ry', `0deg`)
card3d.style.setProperty('--rx', `0deg`)
})
})
return () => (
<div style={style} ref={cardRef}>
{slots.default && slots.default()}
</div>
)
},
})
效果
