|
20 | 20 | let controls: OrbitControls | null = null; |
21 | 21 | let camera: THREE.PerspectiveCamera | null = null; |
22 | 22 | let animationId: number | null = null; |
| 23 | + let isVisible: boolean = false; |
| 24 | + let isModelLoaded: boolean = false; |
| 25 | + let containerElement: HTMLDivElement | null = null; |
| 26 | + let observer: IntersectionObserver | null = null; |
23 | 27 |
|
24 | 28 | function loadModel() { |
25 | 29 | if (!modelUrl) { |
|
56 | 60 | controls.rotateSpeed = 0.5; |
57 | 61 | controls.dampingFactor = 0.1; |
58 | 62 | controls.enableDamping = true; |
59 | | - controls.autoRotate = true; |
| 63 | + controls.autoRotate = false; |
60 | 64 | controls.autoRotateSpeed = 4; |
61 | 65 | controls.update(); |
62 | 66 |
|
|
286 | 290 | ); |
287 | 291 |
|
288 | 292 | const animate = function () { |
| 293 | + if (!isVisible) { |
| 294 | + animationId = null; |
| 295 | + return; |
| 296 | + } |
| 297 | +
|
289 | 298 | animationId = requestAnimationFrame(animate); |
290 | | - controls?.update(); |
| 299 | +
|
| 300 | + if (controls && isVisible) { |
| 301 | + controls.autoRotate = true; |
| 302 | + controls.update(); |
| 303 | + } |
| 304 | +
|
291 | 305 | renderer?.render(scene, camera!); |
292 | 306 | resizeCanvasToDisplaySize(); |
293 | 307 | }; |
294 | | - animate(); |
| 308 | +
|
| 309 | + isModelLoaded = true; |
| 310 | + if (isVisible) { |
| 311 | + animate(); |
| 312 | + } |
| 313 | + } |
| 314 | +
|
| 315 | + function startAnimation() { |
| 316 | + if (animationId === null && isModelLoaded && isVisible && renderer && controls && camera) { |
| 317 | + const animate = function () { |
| 318 | + if (!isVisible) { |
| 319 | + animationId = null; |
| 320 | + return; |
| 321 | + } |
| 322 | +
|
| 323 | + animationId = requestAnimationFrame(animate); |
| 324 | +
|
| 325 | + if (controls && isVisible) { |
| 326 | + controls.autoRotate = true; |
| 327 | + controls.update(); |
| 328 | + } |
| 329 | +
|
| 330 | + renderer?.render(scene, camera!); |
| 331 | +
|
| 332 | + const canvas = renderer?.domElement; |
| 333 | + const width = canvas?.clientWidth; |
| 334 | + const height = canvas?.clientHeight; |
| 335 | + if (canvas?.width !== width || canvas?.height !== height) { |
| 336 | + renderer?.setSize(width ?? 0, height ?? 0, false); |
| 337 | + renderer?.setPixelRatio(window.devicePixelRatio); |
| 338 | + if (camera) camera.aspect = (width ?? 1) / (height ?? 1); |
| 339 | + camera?.updateProjectionMatrix(); |
| 340 | + } |
| 341 | + }; |
| 342 | + animate(); |
| 343 | + } |
| 344 | + } |
| 345 | +
|
| 346 | + function stopAnimation() { |
| 347 | + if (animationId !== null) { |
| 348 | + cancelAnimationFrame(animationId); |
| 349 | + animationId = null; |
| 350 | + } |
| 351 | + if (controls) { |
| 352 | + controls.autoRotate = false; |
| 353 | + } |
295 | 354 | } |
296 | 355 |
|
297 | 356 | onMount(() => { |
| 357 | + observer = new IntersectionObserver( |
| 358 | + (entries) => { |
| 359 | + entries.forEach((entry) => { |
| 360 | + const wasVisible = isVisible; |
| 361 | + isVisible = entry.isIntersecting; |
| 362 | +
|
| 363 | + if (isVisible && !wasVisible && isModelLoaded) { |
| 364 | + startAnimation(); |
| 365 | + } else if (!isVisible && wasVisible) { |
| 366 | + stopAnimation(); |
| 367 | + } |
| 368 | + }); |
| 369 | + }, |
| 370 | + { |
| 371 | + threshold: 0.01, |
| 372 | + rootMargin: '50px' |
| 373 | + } |
| 374 | + ); |
| 375 | +
|
| 376 | + if (containerElement) { |
| 377 | + observer.observe(containerElement); |
| 378 | + } |
| 379 | +
|
298 | 380 | fileSizeFromUrl(modelUrl).then((size) => { |
299 | 381 | fileSize = size; |
300 | 382 |
|
|
307 | 389 | }); |
308 | 390 |
|
309 | 391 | onDestroy(() => { |
310 | | - if (animationId !== null) { |
311 | | - cancelAnimationFrame(animationId); |
| 392 | + stopAnimation(); |
| 393 | +
|
| 394 | + if (observer) { |
| 395 | + observer.disconnect(); |
| 396 | + observer = null; |
312 | 397 | } |
313 | 398 |
|
314 | 399 | controls?.dispose(); |
|
335 | 420 | renderer = null; |
336 | 421 | controls = null; |
337 | 422 | camera = null; |
| 423 | + containerElement = null; |
338 | 424 | }); |
339 | 425 | </script> |
340 | 426 |
|
341 | | -<div class="relative h-full w-full"> |
| 427 | +<div class="relative h-full w-full" bind:this={containerElement}> |
342 | 428 | {#if loadedPercent < 100} |
343 | 429 | <div class="center flex"> |
344 | 430 | {#if showLoadButton} |
|
0 commit comments