fix: heartSection.vue
This commit is contained in:
@ -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
96
src/declarations.d.ts
vendored
@ -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 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
16
src/pages/clicker-game/clickerIndex.vue
Normal file
16
src/pages/clicker-game/clickerIndex.vue
Normal 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>
|
@ -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>
|
||||||
|
@ -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
|
||||||
|
Reference in New Issue
Block a user