728 lines
18 KiB
Vue
728 lines
18 KiB
Vue
<script setup>
|
||
import { ref, onMounted, onUnmounted, computed, getCurrentInstance as vueGetCurrentInstance, watch } from 'vue'
|
||
import ImagePreloader from '@/utils/preload'
|
||
import VideoPlayButton from './VideoPlayButton.vue'
|
||
import VideoPlayerModal from './VideoPlayerModal.vue'
|
||
|
||
// 获取 Vue 实例
|
||
const instance = vueGetCurrentInstance()
|
||
|
||
// 组件属性
|
||
const props = defineProps({
|
||
active: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
scrollPosition: {
|
||
type: Number,
|
||
default: 0
|
||
},
|
||
videoUrl: {
|
||
type: String,
|
||
default: ''
|
||
}
|
||
})
|
||
|
||
const emit = defineEmits(['collect-seal', 'video-open', 'video-close'])
|
||
|
||
// sq3图片显示状态
|
||
const sq3ImageVisible = ref(false)
|
||
// 是否已经收集福印
|
||
const sealCollected = ref(false)
|
||
// 视频播放状态
|
||
const showVideoPlayer = ref(false)
|
||
|
||
// 计算视差效果的偏移量
|
||
const parallaxOffset = computed(() => {
|
||
return props.scrollPosition * 0.1
|
||
})
|
||
|
||
// 动态加载东直门场景图片
|
||
const getDzmImageUrl = (name) => {
|
||
return new URL(`/static/dzm/${name}`, import.meta.url).href
|
||
}
|
||
|
||
// 拖拽状态
|
||
const isDragging = ref(false)
|
||
const showDuck = ref(true) // 调试时默认显示鸭子
|
||
const deskImage = ref(getDzmImageUrl('img_desk1.png'))
|
||
const showGuideElements = ref(true)
|
||
|
||
// Canvas 触摸是否禁用
|
||
const canvasDisabled = ref(false)
|
||
|
||
// 调试模式
|
||
const debugMode = ref(false)
|
||
|
||
// Canvas 上下文
|
||
const ctx = ref(null)
|
||
|
||
// 鸭子图片路径
|
||
const duckImagePath = ref(null)
|
||
|
||
// rpx 转换比例
|
||
const rpxRatio = ref(0.5) // 默认 iPhone 6/7/8 的 375px / 750rpx = 0.5
|
||
|
||
// 鸭子位置和尺寸(使用 rpx 单位,在 750rpx 设计稿下)
|
||
const duckRpx = ref({
|
||
x: 550, // 275px * 2 = 550rpx
|
||
y: 220, // 110px * 2 = 220rpx
|
||
width: 72, // 36px * 2 = 72rpx
|
||
height: 140 // 70px * 2 = 140rpx
|
||
})
|
||
|
||
// 鸭子当前位置(px 单位,用于绘制)
|
||
const duckX = ref(0)
|
||
const duckY = ref(0)
|
||
const duckWidth = ref(0)
|
||
const duckHeight = ref(0)
|
||
|
||
// 拖拽偏移量
|
||
const dragOffsetX = ref(0)
|
||
const dragOffsetY = ref(0)
|
||
|
||
// Canvas 位置和尺寸(使用 rpx 单位)
|
||
const canvasRpx = ref({
|
||
x: 0,
|
||
y: 1550, // 800px * 2 = 1600rpx
|
||
width: 750, // 375px * 2 = 750rpx (全屏宽度)
|
||
height: 600 // 300px * 2 = 600rpx
|
||
})
|
||
|
||
// Canvas 当前位置和尺寸(px 单位)
|
||
const canvasRect = ref({
|
||
left: 0,
|
||
top: 0,
|
||
width: 0,
|
||
height: 0
|
||
})
|
||
|
||
// 目标区域 (餐桌区域) - 使用 rpx 单位
|
||
const targetAreaRpx = ref({
|
||
x: 0,
|
||
y: 160, // 80px * 2 = 160rpx
|
||
width: 400, // 200px * 2 = 400rpx
|
||
height: 300 // 150px * 2 = 300rpx
|
||
})
|
||
|
||
// 目标区域当前位置和尺寸(px 单位)
|
||
const targetArea = ref({
|
||
x: 0,
|
||
y: 0,
|
||
width: 0,
|
||
height: 0
|
||
})
|
||
|
||
// 动画帧 ID
|
||
const animationId = ref(null)
|
||
|
||
// 图片加载状态
|
||
const imagesLoaded = ref(false)
|
||
const isPreloading = ref(false)
|
||
|
||
// 初始化 Canvas
|
||
const initCanvas = () => {
|
||
console.log('正在初始化 Canvas...')
|
||
|
||
// 在 uni-app 中使用 uni.createCanvasContext
|
||
ctx.value = uni.createCanvasContext('dragDuckCanvas', instance)
|
||
|
||
if (ctx.value) {
|
||
console.log('Canvas 上下文创建成功')
|
||
// 加载鸭子图片
|
||
loadDuckImage()
|
||
// 获取 Canvas 位置
|
||
getCanvasPosition()
|
||
// 开始动画循环
|
||
startAnimation()
|
||
} else {
|
||
console.error('Canvas 上下文创建失败')
|
||
}
|
||
}
|
||
|
||
// 加载鸭子图片
|
||
const loadDuckImage = () => {
|
||
uni.getImageInfo({
|
||
src: getDzmImageUrl('img_duck.png'),
|
||
success: (res) => {
|
||
console.log('鸭子图片加载成功:', res)
|
||
duckImagePath.value = res.path
|
||
},
|
||
fail: (err) => {
|
||
console.error('鸭子图片加载失败:', err)
|
||
}
|
||
})
|
||
}
|
||
|
||
// rpx 转 px
|
||
const rpxToPx = (rpx) => {
|
||
return rpx * rpxRatio.value
|
||
}
|
||
|
||
// 更新鸭子位置(根据 rpx 计算 px)
|
||
const updateDuckPosition = () => {
|
||
duckX.value = rpxToPx(duckRpx.value.x)
|
||
duckY.value = rpxToPx(duckRpx.value.y)
|
||
duckWidth.value = rpxToPx(duckRpx.value.width)
|
||
duckHeight.value = rpxToPx(duckRpx.value.height)
|
||
}
|
||
|
||
// 更新目标区域(根据 rpx 计算 px)
|
||
const updateTargetArea = () => {
|
||
targetArea.value = {
|
||
x: rpxToPx(targetAreaRpx.value.x),
|
||
y: rpxToPx(targetAreaRpx.value.y),
|
||
width: rpxToPx(targetAreaRpx.value.width),
|
||
height: rpxToPx(targetAreaRpx.value.height)
|
||
}
|
||
}
|
||
|
||
// 更新 Canvas 位置和尺寸(根据 rpx 计算 px)
|
||
const updateCanvasRect = () => {
|
||
canvasRect.value = {
|
||
left: rpxToPx(canvasRpx.value.x),
|
||
top: rpxToPx(canvasRpx.value.y),
|
||
width: rpxToPx(canvasRpx.value.width),
|
||
height: rpxToPx(canvasRpx.value.height)
|
||
}
|
||
}
|
||
|
||
// Canvas 在视口中的实际位置(用于触摸计算)
|
||
const canvasBoundingRect = ref({
|
||
left: 0,
|
||
top: 0
|
||
})
|
||
|
||
// 获取 Canvas 元素位置
|
||
const getCanvasPosition = () => {
|
||
// 计算 rpx 比例:屏幕宽度 / 750
|
||
const systemInfo = uni.getSystemInfoSync()
|
||
rpxRatio.value = systemInfo.windowWidth / 750
|
||
console.log('屏幕宽度:', systemInfo.windowWidth, 'rpxRatio:', rpxRatio.value)
|
||
|
||
// 更新 Canvas、鸭子和目标区域的 px 值
|
||
updateCanvasRect()
|
||
updateDuckPosition()
|
||
updateTargetArea()
|
||
|
||
console.log('Canvas 位置信息:', canvasRect.value)
|
||
}
|
||
|
||
// 获取 Canvas 在视口中的实际位置(用于触摸坐标计算)
|
||
const getCanvasBoundingRect = () => {
|
||
const query = uni.createSelectorQuery().in(instance)
|
||
query.select('#dragDuckCanvas').boundingClientRect(res => {
|
||
if (res) {
|
||
canvasBoundingRect.value = {
|
||
left: res.left,
|
||
top: res.top
|
||
}
|
||
console.log('Canvas 视口位置:', canvasBoundingRect.value)
|
||
}
|
||
}).exec()
|
||
}
|
||
|
||
// 开始动画循环
|
||
const startAnimation = () => {
|
||
const animate = () => {
|
||
drawCanvas()
|
||
animationId.value = requestAnimationFrame(animate)
|
||
}
|
||
animationId.value = requestAnimationFrame(animate)
|
||
}
|
||
|
||
// 绘制 Canvas
|
||
const drawCanvas = () => {
|
||
if (!ctx.value) return
|
||
|
||
// 清除 Canvas
|
||
ctx.value.clearRect(0, 0, canvasRect.value.width || 400, canvasRect.value.height || 600)
|
||
|
||
// 绘制目标区域
|
||
drawTarget()
|
||
|
||
// 绘制鸭子
|
||
drawDuck()
|
||
|
||
// 执行绘制
|
||
ctx.value.draw(true)
|
||
}
|
||
|
||
// 绘制目标区域
|
||
const drawTarget = () => {
|
||
const target = targetArea.value
|
||
|
||
if (debugMode.value) {
|
||
// 调试模式:显示目标区域边框和文字
|
||
// 绘制目标区域背景
|
||
ctx.value.setFillStyle('rgba(52, 152, 219, 0.3)')
|
||
ctx.value.fillRect(target.x, target.y, target.width, target.height)
|
||
|
||
// 绘制目标区域边框
|
||
ctx.value.setStrokeStyle('#3498db')
|
||
ctx.value.setLineWidth(2)
|
||
ctx.value.setLineDash([5, 5])
|
||
ctx.value.strokeRect(target.x, target.y, target.width, target.height)
|
||
ctx.value.setLineDash([])
|
||
|
||
// 绘制目标区域文字
|
||
ctx.value.setFontSize(14)
|
||
ctx.value.setFillStyle('#3498db')
|
||
ctx.value.setTextAlign('center')
|
||
ctx.value.fillText(
|
||
'目标区域',
|
||
target.x + target.width / 2,
|
||
target.y + target.height / 2
|
||
)
|
||
}
|
||
// 非调试模式:完全透明,不绘制任何内容
|
||
}
|
||
|
||
// 绘制鸭子
|
||
const drawDuck = () => {
|
||
if (!showDuck.value) return
|
||
|
||
// 非调试模式下,只有拖拽时才显示鸭子
|
||
if (!debugMode.value && !isDragging.value) {
|
||
return
|
||
}
|
||
|
||
// 如果正在拖拽,设置半透明
|
||
if (isDragging.value) {
|
||
ctx.value.globalAlpha = 0.6
|
||
} else {
|
||
ctx.value.globalAlpha = 1.0
|
||
}
|
||
|
||
// 如果图片已加载,绘制图片
|
||
if (duckImagePath.value) {
|
||
ctx.value.drawImage(duckImagePath.value, duckX.value, duckY.value, duckWidth.value, duckHeight.value)
|
||
} else {
|
||
// 图片未加载时,绘制占位方块
|
||
ctx.value.setFillStyle('#ffcc00')
|
||
ctx.value.fillRect(duckX.value, duckY.value, duckWidth.value, duckHeight.value)
|
||
|
||
// 绘制边框
|
||
ctx.value.setStrokeStyle('#333')
|
||
ctx.value.setLineWidth(2)
|
||
ctx.value.strokeRect(duckX.value, duckY.value, duckWidth.value, duckHeight.value)
|
||
|
||
// 绘制文字
|
||
ctx.value.setFontSize(14)
|
||
ctx.value.setFillStyle('#FFFFFF')
|
||
ctx.value.setTextAlign('center')
|
||
ctx.value.setTextBaseline('middle')
|
||
ctx.value.fillText('鸭', duckX.value + duckWidth.value / 2, duckY.value + duckHeight.value / 2)
|
||
}
|
||
|
||
// 恢复透明度
|
||
ctx.value.globalAlpha = 1.0
|
||
|
||
// 调试模式下显示拖拽效果红框
|
||
if (debugMode.value && isDragging.value) {
|
||
// 绘制拖拽阴影
|
||
ctx.value.setFillStyle('rgba(231, 76, 60, 0.3)')
|
||
ctx.value.fillRect(duckX.value, duckY.value, duckWidth.value, duckHeight.value)
|
||
|
||
// 绘制拖拽边框
|
||
ctx.value.setStrokeStyle('#e74c3c')
|
||
ctx.value.setLineWidth(2)
|
||
ctx.value.setLineDash([5, 5])
|
||
ctx.value.strokeRect(duckX.value - 3, duckY.value - 3, duckWidth.value + 6, duckHeight.value + 6)
|
||
ctx.value.setLineDash([])
|
||
}
|
||
}
|
||
|
||
// 检查触摸点是否在鸭子内
|
||
const checkTouchInDuck = (x, y) => {
|
||
return x >= duckX.value &&
|
||
x <= duckX.value + duckWidth.value &&
|
||
y >= duckY.value &&
|
||
y <= duckY.value + duckHeight.value
|
||
}
|
||
|
||
// 检查鸭子是否在目标区域内
|
||
const checkDuckInTarget = () => {
|
||
const target = targetArea.value
|
||
|
||
// 计算鸭子中心点
|
||
const duckCenterX = duckX.value + duckWidth.value / 2
|
||
const duckCenterY = duckY.value + duckHeight.value / 2
|
||
|
||
// 检查中心点是否在目标区域内
|
||
return duckCenterX >= target.x &&
|
||
duckCenterX <= target.x + target.width &&
|
||
duckCenterY >= target.y &&
|
||
duckCenterY <= target.y + target.height
|
||
}
|
||
|
||
// 触摸开始事件
|
||
const handleTouchStart = (e) => {
|
||
// 如果 Canvas 已禁用,不处理触摸事件
|
||
if (canvasDisabled.value) {
|
||
console.log('Canvas 已禁用,忽略触摸事件')
|
||
return
|
||
}
|
||
|
||
console.log('触摸开始事件触发', e)
|
||
|
||
// 获取触摸点
|
||
const touch = e.touches[0]
|
||
if (!touch) {
|
||
console.error('没有触摸点信息')
|
||
return
|
||
}
|
||
|
||
// 获取 Canvas 在视口中的实际位置
|
||
getCanvasBoundingRect()
|
||
|
||
// 异步获取 Canvas 位置后执行检查
|
||
setTimeout(() => {
|
||
// 计算相对于 Canvas 的坐标(使用视口位置)
|
||
const touchX = touch.clientX - canvasBoundingRect.value.left
|
||
const touchY = touch.clientY - canvasBoundingRect.value.top
|
||
|
||
console.log(`触摸点 client: (${touch.clientX}, ${touch.clientY})`)
|
||
console.log(`Canvas 视口位置: (${canvasBoundingRect.value.left}, ${canvasBoundingRect.value.top})`)
|
||
console.log(`触摸点相对 Canvas: (${touchX}, ${touchY})`)
|
||
console.log(`鸭子位置: (${duckX.value}, ${duckY.value})`)
|
||
console.log(`鸭子尺寸: (${duckWidth.value}, ${duckHeight.value})`)
|
||
|
||
// 检查是否点击在鸭子上
|
||
if (checkTouchInDuck(touchX, touchY)) {
|
||
console.log('触摸点在鸭子内,开始拖拽')
|
||
|
||
// 设置拖拽状态和偏移量
|
||
isDragging.value = true
|
||
dragOffsetX.value = touchX - duckX.value
|
||
dragOffsetY.value = touchY - duckY.value
|
||
|
||
showDuck.value = true
|
||
} else {
|
||
console.log('触摸点不在鸭子内')
|
||
}
|
||
}, 50)
|
||
}
|
||
|
||
// 触摸移动事件
|
||
const handleTouchMove = (e) => {
|
||
if (!isDragging.value) return
|
||
|
||
e.preventDefault()
|
||
|
||
const touch = e.touches[0]
|
||
if (!touch) return
|
||
|
||
// 计算相对于 Canvas 的坐标(使用视口位置)
|
||
const touchX = touch.clientX - canvasBoundingRect.value.left
|
||
const touchY = touch.clientY - canvasBoundingRect.value.top
|
||
|
||
// 更新鸭子位置
|
||
duckX.value = touchX - dragOffsetX.value
|
||
duckY.value = touchY - dragOffsetY.value
|
||
|
||
// 限制边界
|
||
const canvasW = canvasRect.value.width || 400
|
||
const canvasH = canvasRect.value.height || 600
|
||
|
||
duckX.value = Math.max(0, Math.min(canvasW - duckWidth.value, duckX.value))
|
||
duckY.value = Math.max(0, Math.min(canvasH - duckHeight.value, duckY.value))
|
||
|
||
console.log(`鸭子新位置: (${duckX.value}, ${duckY.value})`)
|
||
}
|
||
|
||
// 触摸结束事件
|
||
const handleTouchEnd = () => {
|
||
console.log('触摸结束事件触发')
|
||
|
||
if (isDragging.value) {
|
||
isDragging.value = false
|
||
|
||
// 检查是否成功放入目标区域
|
||
if (checkDuckInTarget()) {
|
||
console.log('成功放入目标区域!')
|
||
// 更换餐桌图片
|
||
deskImage.value = getDzmImageUrl('img_desk2.png')
|
||
// 隐藏引导元素
|
||
showGuideElements.value = false
|
||
// 隐藏鸭子
|
||
showDuck.value = false
|
||
// 禁用 Canvas 触摸事件
|
||
canvasDisabled.value = true
|
||
console.log('Canvas 触摸事件已禁用')
|
||
|
||
// 第一次成功放入目标区域时显示sq3图片并收集福印
|
||
if (!sealCollected.value) {
|
||
sq3ImageVisible.value = true
|
||
sealCollected.value = true
|
||
emit('collect-seal')
|
||
}
|
||
} else {
|
||
// 未放入目标区域,回归到原始位置
|
||
console.log('未放入目标区域,回归原始位置')
|
||
resetDuckPosition()
|
||
}
|
||
}
|
||
}
|
||
|
||
// 重置鸭子位置到初始值
|
||
const resetDuckPosition = () => {
|
||
// 恢复原始 rpx 值
|
||
duckRpx.value.x = 550
|
||
duckRpx.value.y = 220
|
||
// 更新 px 值
|
||
updateDuckPosition()
|
||
console.log('鸭子已回归原始位置:', duckX.value, duckY.value)
|
||
}
|
||
|
||
// 打开视频播放器
|
||
const openVideoPlayer = () => {
|
||
showVideoPlayer.value = true
|
||
emit('video-open')
|
||
}
|
||
|
||
// 关闭视频播放器
|
||
const closeVideoPlayer = () => {
|
||
showVideoPlayer.value = false
|
||
emit('video-close')
|
||
}
|
||
|
||
// 预加载图片
|
||
const preloadImages = async () => {
|
||
if (imagesLoaded.value || isPreloading.value) return
|
||
|
||
isPreloading.value = true
|
||
try {
|
||
// 需要预加载的图片列表
|
||
const imageUrls = [
|
||
getDzmImageUrl('img_duck.png'),
|
||
getDzmImageUrl('img_desk1.png'),
|
||
getDzmImageUrl('img_desk2.png'),
|
||
getDzmImageUrl('img_stove1.png'),
|
||
getDzmImageUrl('img_line.png'),
|
||
'/static/images/icon_hand.png'
|
||
]
|
||
|
||
// 使用 ImagePreloader 批量预加载图片
|
||
await ImagePreloader.preloadAll(imageUrls)
|
||
imagesLoaded.value = true
|
||
console.log('东直门场景图片预加载完成')
|
||
} catch (error) {
|
||
console.error('图片预加载失败:', error)
|
||
} finally {
|
||
isPreloading.value = false
|
||
}
|
||
}
|
||
|
||
// 开始拖拽(从触发区域开始)
|
||
const startDrag = (e) => {
|
||
console.log('开始拖拽', e)
|
||
|
||
// 初始化 Canvas
|
||
if (!ctx.value) {
|
||
initCanvas()
|
||
}
|
||
|
||
// 设置鸭子初始位置(使用 rpx 值,会自动转换为 px)
|
||
duckRpx.value.x = 100 // 100rpx 对应约 50px (iPhone 6/7/8)
|
||
duckRpx.value.y = 100
|
||
updateDuckPosition()
|
||
showDuck.value = true
|
||
|
||
// 触发触摸开始事件处理
|
||
handleTouchStart(e)
|
||
}
|
||
|
||
// 监听激活状态变化,当组件激活时预加载图片
|
||
watch(() => props.active, async (newActive) => {
|
||
if (newActive) {
|
||
await preloadImages()
|
||
}
|
||
})
|
||
|
||
// 页面挂载时的初始化
|
||
onMounted(() => {
|
||
// 添加动画类,触发入场动画
|
||
const container = document.querySelector('.dongzhimen-scene-container')
|
||
if (container) {
|
||
container.classList.add('animate-in')
|
||
}
|
||
|
||
// 预加载图片
|
||
preloadImages()
|
||
|
||
// 调试时自动初始化 Canvas
|
||
setTimeout(() => {
|
||
initCanvas()
|
||
}, 500)
|
||
})
|
||
|
||
// 组件卸载时清理
|
||
onUnmounted(() => {
|
||
// 停止动画
|
||
if (animationId.value) {
|
||
cancelAnimationFrame(animationId.value)
|
||
}
|
||
})
|
||
</script>
|
||
|
||
<template>
|
||
<view class="dongzhimen-scene-container" :class="{ 'active': active }">
|
||
<!-- 背景图片层 -->
|
||
<view class="background-layer" :style="{ transform: `translateY(${parallaxOffset}px)` }">
|
||
<image src="/static/bg/bg5.jpg" alt="东直门商圈" class="background-image" mode="widthFix" />
|
||
</view>
|
||
|
||
<!-- sq3图片 -->
|
||
<image
|
||
v-if="sq3ImageVisible"
|
||
src="/static/images/sq5.png"
|
||
alt="新春祝福"
|
||
class="sq-image"
|
||
mode="widthFix"
|
||
/>
|
||
|
||
<!-- 装饰图片 -->
|
||
<image :src="deskImage" alt="餐桌" class="deco-img desk-img" mode="widthFix" />
|
||
<image :src="getDzmImageUrl('img_stove1.png')" alt="灶台" class="deco-img stove-img" mode="widthFix" />
|
||
<image v-if="showGuideElements" :src="getDzmImageUrl('img_line.png')" alt="线条" class="deco-img line-img" mode="widthFix" />
|
||
<image v-if="showGuideElements" src="/static/images/icon_hand.png" alt="手势" class="deco-img hand-img" mode="widthFix" />
|
||
|
||
<!-- Canvas 拖拽区域 -->
|
||
<canvas
|
||
canvas-id="dragDuckCanvas"
|
||
id="dragDuckCanvas"
|
||
class="drag-canvas"
|
||
:style="{
|
||
left: canvasRect.left + 'px',
|
||
top: canvasRect.top + 'px',
|
||
width: canvasRect.width + 'px',
|
||
height: canvasRect.height + 'px'
|
||
}"
|
||
@touchstart="handleTouchStart"
|
||
@touchmove="handleTouchMove"
|
||
@touchend="handleTouchEnd"
|
||
></canvas>
|
||
|
||
<!-- 拖拽触发区域 -->
|
||
<view
|
||
v-if="showGuideElements"
|
||
class="drag-trigger-area"
|
||
@touchstart="startDrag"
|
||
></view>
|
||
|
||
<!-- 视频播放按钮 -->
|
||
<VideoPlayButton @play="openVideoPlayer" />
|
||
|
||
<!-- 视频播放器弹窗 -->
|
||
<VideoPlayerModal
|
||
:video-url="props.videoUrl"
|
||
:visible="showVideoPlayer"
|
||
@close="closeVideoPlayer"
|
||
/>
|
||
|
||
</view>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.dongzhimen-scene-container {
|
||
position: relative;
|
||
width: 100%;
|
||
height: auto;
|
||
overflow: hidden;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
background-color: #ff6b35;
|
||
}
|
||
|
||
.background-layer {
|
||
position: relative;
|
||
width: 100%;
|
||
transition: transform 0.1s ease;
|
||
height: auto;
|
||
}
|
||
|
||
.background-image {
|
||
width: 100%;
|
||
height: auto;
|
||
display: block;
|
||
}
|
||
|
||
.sq-image {
|
||
position: absolute;
|
||
top: 1220rpx;
|
||
right: -6rpx;
|
||
width: 300rpx;
|
||
height: auto;
|
||
z-index: 20;
|
||
animation: fadeIn 0.5s ease;
|
||
}
|
||
|
||
@keyframes fadeIn {
|
||
from { opacity: 0; transform: translateY(-20px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
|
||
.deco-img {
|
||
position: absolute;
|
||
z-index: 25;
|
||
}
|
||
|
||
.desk-img {
|
||
left: 13rpx;
|
||
top: 1665rpx;
|
||
width: 441rpx;
|
||
height: auto;
|
||
}
|
||
|
||
.stove-img {
|
||
left: 492rpx;
|
||
top: 1711rpx;
|
||
width: 241rpx;
|
||
height: auto;
|
||
}
|
||
|
||
.line-img {
|
||
left: 250rpx;
|
||
top: 1842rpx;
|
||
width: 360rpx;
|
||
height: auto;
|
||
}
|
||
|
||
.hand-img {
|
||
left: 440rpx;
|
||
top: 1900rpx;
|
||
width: 38rpx;
|
||
height: auto;
|
||
animation: arcSlideLeft 1.2s ease-in-out infinite;
|
||
}
|
||
|
||
@keyframes arcSlideLeft {
|
||
0% {
|
||
transform: translateX(0) translateY(0);
|
||
opacity: 1;
|
||
}
|
||
100% {
|
||
transform: translateX(-80rpx) translateY(3rpx);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
.drag-canvas {
|
||
position: absolute;
|
||
z-index: 100;
|
||
pointer-events: auto;
|
||
}
|
||
|
||
.drag-trigger-area {
|
||
position: absolute;
|
||
left: 540rpx;
|
||
top: 1781rpx;
|
||
width: 100rpx;
|
||
height: 100rpx;
|
||
z-index: 30;
|
||
}
|
||
|
||
|
||
</style> |