fix: heartSection.vue

This commit is contained in:
2025-08-02 22:34:43 +04:00
parent faa4663e20
commit 5e5713bde0
6 changed files with 131 additions and 63 deletions

View File

@ -1,17 +1,34 @@
<template> <template>
<section> <div ref="container" :class="sizeStyle" />
<div ref="container" class="w-full h-screen" />
</section>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, onBeforeUnmount, ref } from 'vue' import {onMounted, onBeforeUnmount, ref, computed} from 'vue'
import { useRouter } from 'vue-router'
import * as THREE from 'three' import * as THREE from 'three'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader' import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { heartSectionConst } from '../../../constants' import { heartSectionConst } from '../constants'
const props = defineProps<{
mode: 'home' | 'game',
size?: 'primary'
}>()
const emit = defineEmits<{
(e: 'click'): void
}>()
const sizeStyle = computed(() => {
switch (props.size) {
case 'primary':
default:
return 'w-full h-screen';
}
})
const container = ref<HTMLDivElement | null>(null) const container = ref<HTMLDivElement | null>(null)
const router = useRouter()
let scene: THREE.Scene let scene: THREE.Scene
let camera: THREE.PerspectiveCamera let camera: THREE.PerspectiveCamera
@ -21,11 +38,15 @@ let heart: THREE.Group | null = null
let { SCALE: scale, ANIMATION_ID: animationId, DIRECTION: direction } = heartSectionConst let { SCALE: scale, ANIMATION_ID: animationId, DIRECTION: direction } = heartSectionConst
const raycaster = new THREE.Raycaster()
const mouse = new THREE.Vector2()
const animate = () => { const animate = () => {
animationId = requestAnimationFrame(animate) animationId = requestAnimationFrame(animate)
if (heart) { if (heart) {
scale += 0.003 * direction scale += 0.003 * direction
if (scale > 1.05 || scale < 0.95) return (direction *= -1) if (scale > 1.05 || scale < 0.95) direction *= -1
heart.scale.set(scale, scale, scale) heart.scale.set(scale, scale, scale)
heart.rotation.y += 0.005 heart.rotation.y += 0.005
} }
@ -34,6 +55,25 @@ const animate = () => {
renderer.render(scene, camera) renderer.render(scene, camera)
} }
const onClick = (event: MouseEvent) => {
if (!container.value || !heart) return
const rect = container.value.getBoundingClientRect()
mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1
mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1
raycaster.setFromCamera(mouse, camera)
const intersects = raycaster.intersectObject(heart, true)
if (intersects.length > 0) {
if (props.mode === 'home') {
router.push('/game')
} else if (props.mode === 'game') {
emit('click')
}
}
}
onMounted(() => { onMounted(() => {
if (!container.value) return if (!container.value) return
@ -41,13 +81,12 @@ onMounted(() => {
scene.background = new THREE.Color(0x000000) scene.background = new THREE.Color(0x000000)
camera = new THREE.PerspectiveCamera( camera = new THREE.PerspectiveCamera(
75, 75,
container.value.clientWidth / container.value.clientHeight, container.value.clientWidth / container.value.clientHeight,
0.1, 0.1,
1000, 1000
) )
camera.position.set(0, 1, 5) camera.position.set(0, 1, 5)
camera.position.set(0, 1, 5)
renderer = new THREE.WebGLRenderer({ antialias: true }) renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(container.value.clientWidth, container.value.clientHeight) renderer.setSize(container.value.clientWidth, container.value.clientHeight)
@ -70,6 +109,7 @@ onMounted(() => {
}) })
window.addEventListener('resize', onWindowResize) window.addEventListener('resize', onWindowResize)
window.addEventListener('click', onClick)
animate() animate()
}) })
@ -77,6 +117,7 @@ onMounted(() => {
onBeforeUnmount(() => { onBeforeUnmount(() => {
cancelAnimationFrame(animationId) cancelAnimationFrame(animationId)
window.removeEventListener('resize', onWindowResize) window.removeEventListener('resize', onWindowResize)
window.removeEventListener('click', onClick)
}) })
const onWindowResize = () => { const onWindowResize = () => {

96
src/declarations.d.ts vendored
View File

@ -1,66 +1,66 @@
declare module 'three/examples/jsm/loaders/GLTFLoader' { declare module 'three/examples/jsm/loaders/GLTFLoader' {
import { Loader, LoadingManager, Group, AnimationClip, Object3D } from 'three' import { Loader, LoadingManager, Group, AnimationClip, Object3D } from 'three'
export interface GLTF { export interface GLTF {
scene: Group scene: Group
scenes: Group[] scenes: Group[]
animations: AnimationClip[] animations: AnimationClip[]
cameras: Object3D[] cameras: Object3D[]
asset: Record<string, unknown> asset: Record<string, unknown>
} }
export class GLTFLoader extends Loader { export class GLTFLoader extends Loader {
constructor(manager?: LoadingManager) constructor(manager?: LoadingManager)
load( load(
url: string, url: string,
onLoad: (gltf: GLTF) => void, onLoad: (gltf: GLTF) => void,
onProgress?: (event: ProgressEvent<EventTarget>) => void, onProgress?: (event: ProgressEvent<EventTarget>) => void,
onError?: (event: ErrorEvent | unknown) => void onError?: (event: ErrorEvent | unknown) => void,
): void ): void
} }
} }
declare module 'three/examples/jsm/controls/OrbitControls' { declare module 'three/examples/jsm/controls/OrbitControls' {
import { Camera, EventDispatcher, MOUSE, TOUCH, Vector3 } from 'three' import { Camera, EventDispatcher, MOUSE, TOUCH, Vector3 } from 'three'
export class OrbitControls extends EventDispatcher { export class OrbitControls extends EventDispatcher {
constructor(object: Camera, domElement?: HTMLElement) constructor(object: Camera, domElement?: HTMLElement)
object: Camera object: Camera
target: Vector3 target: Vector3
update(): boolean update(): boolean
dispose(): void dispose(): void
enabled: boolean enabled: boolean
autoRotate: boolean autoRotate: boolean
autoRotateSpeed: number autoRotateSpeed: number
minDistance: number minDistance: number
maxDistance: number maxDistance: number
minZoom: number minZoom: number
maxZoom: number maxZoom: number
minPolarAngle: number minPolarAngle: number
maxPolarAngle: number maxPolarAngle: number
minAzimuthAngle: number minAzimuthAngle: number
maxAzimuthAngle: number maxAzimuthAngle: number
enableDamping: boolean enableDamping: boolean
dampingFactor: number dampingFactor: number
enableZoom: boolean enableZoom: boolean
zoomSpeed: number zoomSpeed: number
enableRotate: boolean enableRotate: boolean
rotateSpeed: number rotateSpeed: number
enablePan: boolean enablePan: boolean
panSpeed: number panSpeed: number
screenSpacePanning: boolean screenSpacePanning: boolean
keyPanSpeed: number keyPanSpeed: number
mouseButtons: { LEFT: MOUSE; MIDDLE: MOUSE; RIGHT: MOUSE } mouseButtons: { LEFT: MOUSE; MIDDLE: MOUSE; RIGHT: MOUSE }
touches: { ONE: TOUCH; TWO: TOUCH } touches: { ONE: TOUCH; TWO: TOUCH }
} }
} }

View File

@ -1,7 +1,13 @@
<template> <template>
<header> <header>
<div class="flex items-center justify-between hidden">hidden</div> <div class="bg-black flex items-center justify-center flex-col">
<UiHeadering class="text-white" tag="H1">Heart Clicker 💖</UiHeadering>
<UiParagraph class="text-white">Кликай по сердцу, чтобы собрать любовь!</UiParagraph>
</div>
</header> </header>
</template> </template>
<script setup lang="ts"></script> <script setup lang="ts">
import UiHeadering from "../components/typography/UiHeadering.vue";
import UiParagraph from "../components/typography/UiParagraph.vue";
</script>

View File

@ -0,0 +1,16 @@
<template>
<section class="min-h-screen bg-black text-white">
<div class="flex items-center justify-center container mx-auto">
<HeartSection mode="game" @click="clickCount++" />
<UiParagraph class="text-white">{{clickCount}}</UiParagraph>
</div>
</section>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import HeartSection from '../../components/heartSection.vue'
import UiParagraph from "../../components/typography/UiParagraph.vue";
const clickCount = ref(0)
</script>

View File

@ -1,9 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import HeartSection from './_ui/heartSection.vue' import HeartSection from '../../components/heartSection.vue'
</script> </script>
<template> <template>
<section> <section>
<HeartSection /> <HeartSection mode="home"/>
</section> </section>
</template> </template>

View File

@ -6,5 +6,10 @@ const routes: TRoute[] = [
name: '/', name: '/',
component: () => import('../../pages/index/rootPage.vue'), component: () => import('../../pages/index/rootPage.vue'),
}, },
{
path:'/game',
name:'/game',
component:()=> import('../../pages/clicker-game/clickerIndex.vue')
}
] ]
export default routes export default routes