1import{r as u,k as e,y as v,x as s,T as o}from"./index-Bn1bbOAe.js";import{u as g,C as P,T as p,P as M,O as I,a as x,b as y,c as f}from"./useComponentProps-CkrCaLvB.js";import{D as E}from"./Dependencies-BritF_2p.js";import{u as L}from"./useForceRerender-CvsfwHEi.js";import{C as T}from"./Customize-DBLruEgy.js";import{P as b}from"./PreviewSelect-CW4bTxJK.js";import R from"./ImageTrail-Cz7kGx8T.js";import"./index-CKdJydTQ.js";import"./backgroundThemeProps-BAOOEp3Q.js";import"./ShikiCode-CUach_VP.js";import"./aiExport--nnywH9U.js";import"./IconSelect-CgfYlisg.js";import"./sparkles-DtoGwhqa.js";import"./maximize-2-DQy3aI7-.js";import"./copy--0PRdOLE.js";import"./preview-slider-B9GA5uEa.js";const D=`'use client'; 2 3import { useRef, useEffect } from 'react'; 4import { gsap } from 'gsap'; 5 6import './ImageTrail.css'; 7 8function lerp(a, b, n) { 9 return (1 - n) * a + n * b; 10} 11 12function getLocalPointerPos(e, rect) { 13 let clientX = 0, 14 clientY = 0; 15 if (e.touches && e.touches.length > 0) { 16 clientX = e.touches[0].clientX; 17 clientY = e.touches[0].clientY; 18 } else { 19 clientX = e.clientX; 20 clientY = e.clientY; 21 } 22 return { 23 x: clientX - rect.left, 24 y: clientY - rect.top 25 }; 26} 27function getMouseDistance(p1, p2) { 28 const dx = p1.x - p2.x; 29 const dy = p1.y - p2.y; 30 return Math.hypot(dx, dy); 31} 32 33class ImageItem { 34 DOM = { el: null, inner: null }; 35 defaultStyle = { scale: 1, x: 0, y: 0, opacity: 0 }; 36 rect = null; 37 38 constructor(DOM_el) { 39 this.DOM.el = DOM_el; 40 this.DOM.inner = this.DOM.el.querySelector('.content__img-inner'); 41 this.getRect(); 42 this.initEvents(); 43 } 44 initEvents() { 45 this.resize = () => { 46 gsap.set(this.DOM.el, this.defaultStyle); 47 this.getRect(); 48 }; 49 window.addEventListener('resize', this.resize); 50 } 51 getRect() { 52 this.rect = this.DOM.el.getBoundingClientRect(); 53 } 54 55 destroy() { 56 window.removeEventListener('resize', this.resize); 57 } 58} 59 60class ImageTrailVariant1 { 61 constructor(container) { 62 this.container = container; 63 this.rafId = null; 64 this.destroyed = false; 65 this.DOM = { el: container }; 66 this.images = [...this.DOM.el.querySelectorAll('.content__img')].map(img => new ImageItem(img)); 67 this.imagesTotal = this.images.length; 68 this.imgPosition = 0; 69 this.zIndexVal = 1; 70 this.activeImagesCount = 0; 71 this.isIdle = true; 72 this.threshold = 80; 73 74 this.mousePos = { x: 0, y: 0 }; 75 this.lastMousePos = { x: 0, y: 0 }; 76 this.cacheMousePos = { x: 0, y: 0 }; 77 78 const handlePointerMove = ev => { 79 const rect = this.container.getBoundingClientRect(); 80 this.mousePos = getLocalPointerPos(ev, rect); 81 }; 82 container.addEventListener('mousemove', handlePointerMove); 83 container.addEventListener('touchmove', handlePointerMove); 84 85 const initRender = ev => { 86 const rect = this.container.getBoundingClientRect(); 87 this.mousePos = getLocalPointerPos(ev, rect); 88 this.cacheMousePos = { ...this.mousePos }; 89 90 this.rafId = requestAnimationFrame(() => this.render()); 91 92 container.removeEventListener('mousemove', initRender); 93 container.removeEventListener('touchmove', initRender); 94 }; 95 container.addEventListener('mousemove', initRender); 96 container.addEventListener('touchmove', initRender); 97 this.handlePointerMove = handlePointerMove; 98 this.initRender = initRender; 99 } 100 101 render() { 102 if (this.destroyed) return; 103 104 let distance = getMouseDistance(this.mousePos, this.lastMousePos); 105 this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); 106 this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); 107 108 if (distance > this.threshold) { 109 this.showNextImage(); 110 this.lastMousePos = { ...this.mousePos }; 111 } 112 if (this.isIdle && this.zIndexVal !== 1) { 113 this.zIndexVal = 1; 114 } 115 this.rafId = requestAnimationFrame(() => this.render()); 116 } 117 118 showNextImage() { 119 ++this.zIndexVal; 120 this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0; 121 const img = this.images[this.imgPosition]; 122 123 gsap.killTweensOf(img.DOM.el); 124 gsap
125 .timeline({ 126 onStart: () => this.onImageActivated(), 127 onComplete: () => this.onImageDeactivated() 128 }) 129 .fromTo( 130 img.DOM.el, 131 { 132 opacity: 1, 133 scale: 1, 134 zIndex: this.zIndexVal, 135 x: this.cacheMousePos.x - img.rect.width / 2, 136 y: this.cacheMousePos.y - img.rect.height / 2 137 }, 138 { 139 duration: 0.4, 140 ease: 'power1', 141 x: this.mousePos.x - img.rect.width / 2, 142 y: this.mousePos.y - img.rect.height / 2 143 }, 144 0 145 ) 146 .to( 147 img.DOM.el, 148 { 149 duration: 0.4, 150 ease: 'power3', 151 opacity: 0, 152 scale: 0.2 153 }, 154 0.4 155 ); 156 } 157 158 destroy() { 159 this.destroyed = true; 160 if (this.rafId !== null) { 161 cancelAnimationFrame(this.rafId); 162 this.rafId = null; 163 } 164 this.container.removeEventListener('mousemove', this.handlePointerMove); 165 this.container.removeEventListener('touchmove', this.handlePointerMove); 166 this.container.removeEventListener('mousemove', this.initRender); 167 this.container.removeEventListener('touchmove', this.initRender); 168 this.images.forEach(img => { 169 gsap.killTweensOf(img.DOM.el); 170 img.destroy(); 171 }); 172 } 173 174 onImageActivated() { 175 this.activeImagesCount++; 176 this.isIdle = false; 177 } 178 onImageDeactivated() { 179 this.activeImagesCount--; 180 if (this.activeImagesCount === 0) { 181 this.isIdle = true; 182 } 183 } 184} 185 186class ImageTrailVariant2 { 187 constructor(container) { 188 this.container = container; 189 this.rafId = null; 190 this.destroyed = false; 191 this.DOM = { el: container }; 192 this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img)); 193 this.imagesTotal = this.images.length; 194 this.imgPosition = 0; 195 this.zIndexVal = 1; 196 this.activeImagesCount = 0; 197 this.isIdle = true; 198 this.threshold = 80; 199 200 this.mousePos = { x: 0, y: 0 }; 201 this.lastMousePos = { x: 0, y: 0 }; 202 this.cacheMousePos = { x: 0, y: 0 }; 203 204 const handlePointerMove = ev => { 205 const rect = container.getBoundingClientRect(); 206 this.mousePos = getLocalPointerPos(ev, rect); 207 }; 208 container.addEventListener('mousemove', handlePointerMove); 209 container.addEventListener('touchmove', handlePointerMove); 210 211 const initRender = ev => { 212 const rect = container.getBoundingClientRect(); 213 this.mousePos = getLocalPointerPos(ev, rect); 214 this.cacheMousePos = { ...this.mousePos }; 215 216 this.rafId = requestAnimationFrame(() => this.render()); 217 218 container.removeEventListener('mousemove', initRender); 219 container.removeEventListener('touchmove', initRender); 220 }; 221 container.addEventListener('mousemove', initRender); 222 container.addEventListener('touchmove', initRender); 223 this.handlePointerMove = handlePointerMove; 224 this.initRender = initRender; 225 } 226 227 render() { 228 if (this.destroyed) return; 229 230 let distance = getMouseDistance(this.mousePos, this.lastMousePos); 231 this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); 232 this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); 233 234 if (distance > this.threshold) { 235 this.showNextImage(); 236 this.lastMousePos = { ...this.mousePos }; 237 } 238 if (this.isIdle && this.zIndexVal !== 1) { 239 this.zIndexVal = 1; 240 } 241 this.rafId = requestAnimationFrame(() => this.render()); 242 } 243 244 showNextImage() { 245 ++this.zIndexVal; 246 this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0; 247 const img = this.images[this.imgPosition]; 248 249 gsap.killTweensOf(img.DOM.el); 250 gsap 251 .timeline({ 252 onStart: () => this.onImageActivated(), 253 onComplete: () => this.onImageDeactivated() 254 }) 255 .fromTo( 256 img.DOM.el, 257 { 258 opacity: 1, 259 scale: 0, 260 zIndex: this.zIndexVal, 261 x: this.cacheMousePos.x - img.rect.width / 2, 262 y: this.cacheMousePos.y - img.rect.height / 2 263 }, 264 { 265 duration: 0.4, 266 ease: 'power1', 267 scale: 1, 268 x: this.mousePos.x - img.rect.width / 2, 269 y: this.mousePos.y - img.rect.height / 2 270 }, 271 0 272 ) 273 .fromTo( 274 img.DOM.inner, 275 { 276 scale: 2.8, 277 filter: 'brightness(250%)' 278 }, 279 { 280 duration: 0.4, 281 ease: 'power1', 282 scale: 1, 283 filter: 'brightness(100%)' 284 }, 285 0 286 ) 287 .to( 288 img.DOM.el, 289 { 290 duration: 0.4, 291 ease: 'power2', 292 opacity: 0, 293 scale: 0.2 294 }, 295 0.45 296 ); 297 } 298 299 destroy() { 300 this.destroyed = true; 301 if (this.rafId !== null) { 302 cancelAnimationFrame(this.rafId); 303 this.rafId = null; 304 } 305 this.container.removeEventListener('mousemove', this.handlePointerMove); 306 this.container.removeEventListener('touchmove', this.handlePointerMove); 307 this.container.removeEventListener('mousemove', this.initRender); 308 this.container.removeEventListener('touchmove', this.initRender);
309 this.images.forEach(img => { 310 gsap.killTweensOf(img.DOM.el); 311 img.destroy(); 312 }); 313 } 314 315 onImageActivated() { 316 this.activeImagesCount++; 317 this.isIdle = false; 318 } 319 onImageDeactivated() { 320 this.activeImagesCount--; 321 if (this.activeImagesCount === 0) this.isIdle = true; 322 } 323} 324 325class ImageTrailVariant3 { 326 constructor(container) { 327 this.container = container; 328 this.rafId = null; 329 this.destroyed = false; 330 this.DOM = { el: container }; 331 this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img)); 332 this.imagesTotal = this.images.length; 333 this.imgPosition = 0; 334 this.zIndexVal = 1; 335 this.activeImagesCount = 0; 336 this.isIdle = true; 337 this.threshold = 80; 338 339 this.mousePos = { x: 0, y: 0 }; 340 this.lastMousePos = { x: 0, y: 0 }; 341 this.cacheMousePos = { x: 0, y: 0 }; 342 343 const handlePointerMove = ev => { 344 const rect = container.getBoundingClientRect(); 345 this.mousePos = getLocalPointerPos(ev, rect); 346 }; 347 container.addEventListener('mousemove', handlePointerMove); 348 container.addEventListener('touchmove', handlePointerMove); 349 350 const initRender = ev => { 351 const rect = container.getBoundingClientRect(); 352 this.mousePos = getLocalPointerPos(ev, rect); 353 this.cacheMousePos = { ...this.mousePos }; 354 355 this.rafId = requestAnimationFrame(() => this.render()); 356 container.removeEventListener('mousemove', initRender); 357 container.removeEventListener('touchmove', initRender); 358 }; 359 container.addEventListener('mousemove', initRender); 360 container.addEventListener('touchmove', initRender); 361 this.handlePointerMove = handlePointerMove; 362 this.initRender = initRender; 363 } 364 365 render() { 366 if (this.destroyed) return; 367 368 let distance = getMouseDistance(this.mousePos, this.lastMousePos); 369 this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); 370 this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); 371 372 if (distance > this.threshold) { 373 this.showNextImage(); 374 this.lastMousePos = { ...this.mousePos }; 375 } 376 if (this.isIdle && this.zIndexVal !== 1) { 377 this.zIndexVal = 1; 378 } 379 this.rafId = requestAnimationFrame(() => this.render()); 380 } 381 382 showNextImage() { 383 ++this.zIndexVal; 384 this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0; 385 const img = this.images[this.imgPosition]; 386 gsap.killTweensOf(img.DOM.el); 387 388 gsap 389 .timeline({ 390 onStart: () => this.onImageActivated(), 391 onComplete: () => this.onImageDeactivated() 392 }) 393 .fromTo( 394 img.DOM.el, 395 { 396 opacity: 1, 397 scale: 0, 398 zIndex: this.zIndexVal, 399 xPercent: 0, 400 yPercent: 0, 401 x: this.cacheMousePos.x - img.rect.width / 2, 402 y: this.cacheMousePos.y - img.rect.height / 2 403 }, 404 { 405 duration: 0.4, 406 ease: 'power1', 407 scale: 1, 408 x: this.mousePos.x - img.rect.width / 2, 409 y: this.mousePos.y - img.rect.height / 2 410 }, 411 0 412 ) 413 .fromTo( 414 img.DOM.inner, 415 { 416 scale: 1.2 417 }, 418 { 419 duration: 0.4, 420 ease: 'power1', 421 scale: 1 422 }, 423 0 424 ) 425 .to( 426 img.DOM.el, 427 { 428 duration: 0.6, 429 ease: 'power2', 430 opacity: 0, 431 scale: 0.2, 432 xPercent: () => gsap.utils.random(-30, 30), 433 yPercent: -200 434 }, 435 0.6 436 ); 437 } 438 439 destroy() { 440 this.destroyed = true; 441 if (this.rafId !== null) { 442 cancelAnimationFrame(this.rafId); 443 this.rafId = null; 444 } 445 this.container.removeEventListener('mousemove', this.handlePointerMove); 446 this.container.removeEventListener('touchmove', this.handlePointerMove); 447 this.container.removeEventListener('mousemove', this.initRender); 448 this.container.removeEventListener('touchmove', this.initRender); 449 this.images.forEach(img => { 450 gsap.killTweensOf(img.DOM.el); 451 img.destroy(); 452 }); 453 } 454 455 onImageActivated() { 456 this.activeImagesCount++; 457 this.isIdle = false;
458 } 459 onImageDeactivated() { 460 this.activeImagesCount--; 461 if (this.activeImagesCount === 0) this.isIdle = true; 462 } 463} 464 465class ImageTrailVariant4 { 466 constructor(container) { 467 this.container = container; 468 this.rafId = null; 469 this.destroyed = false; 470 this.DOM = { el: container }; 471 this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img)); 472 this.imagesTotal = this.images.length; 473 this.imgPosition = 0; 474 this.zIndexVal = 1; 475 this.activeImagesCount = 0; 476 this.isIdle = true; 477 this.threshold = 80; 478 479 this.mousePos = { x: 0, y: 0 }; 480 this.lastMousePos = { x: 0, y: 0 }; 481 this.cacheMousePos = { x: 0, y: 0 }; 482 483 const handlePointerMove = ev => { 484 const rect = container.getBoundingClientRect(); 485 this.mousePos = getLocalPointerPos(ev, rect); 486 }; 487 container.addEventListener('mousemove', handlePointerMove); 488 container.addEventListener('touchmove', handlePointerMove); 489 490 const initRender = ev => { 491 const rect = container.getBoundingClientRect(); 492 this.mousePos = getLocalPointerPos(ev, rect); 493 this.cacheMousePos = { ...this.mousePos }; 494 this.rafId = requestAnimationFrame(() => this.render()); 495 container.removeEventListener('mousemove', initRender); 496 container.removeEventListener('touchmove', initRender); 497 }; 498 container.addEventListener('mousemove', initRender); 499 container.addEventListener('touchmove', initRender); 500 this.handlePointerMove = handlePointerMove; 501 this.initRender = initRender; 502 } 503 504 render() { 505 if (this.destroyed) return; 506 507 let distance = getMouseDistance(this.mousePos, this.lastMousePos); 508 if (distance > this.threshold) { 509 this.showNextImage(); 510 this.lastMousePos = { ...this.mousePos }; 511 } 512 this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); 513 this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); 514 515 if (this.isIdle && this.zIndexVal !== 1) this.zIndexVal = 1; 516 this.rafId = requestAnimationFrame(() => this.render()); 517 } 518 519 showNextImage() { 520 ++this.zIndexVal; 521 this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0; 522 const img = this.images[this.imgPosition]; 523 gsap.killTweensOf(img.DOM.el); 524 525 let dx = this.mousePos.x - this.cacheMousePos.x; 526 let dy = this.mousePos.y - this.cacheMousePos.y; 527 let distance = Math.sqrt(dx * dx + dy * dy); 528 if (distance !== 0) { 529 dx /= distance; 530 dy /= distance; 531 } 532 dx *= distance / 100; 533 dy *= distance / 100; 534 535 gsap 536 .timeline({ 537 onStart: () => this.onImageActivated(), 538 onComplete: () => this.onImageDeactivated() 539 }) 540 .fromTo( 541 img.DOM.el, 542 { 543 opacity: 1, 544 scale: 0, 545 zIndex: this.zIndexVal, 546 x: this.cacheMousePos.x - img.rect.width / 2, 547 y: this.cacheMousePos.y - img.rect.height / 2 548 }, 549 { 550 duration: 0.4, 551 ease: 'power1', 552 scale: 1, 553 x: this.mousePos.x - img.rect.width / 2, 554 y: this.mousePos.y - img.rect.height / 2 555 }, 556 0 557 ) 558 .fromTo( 559 img.DOM.inner, 560 { 561 scale: 2, 562 filter: \`brightness(\${Math.max((400 * distance) / 100, 100)}%) contrast(\${Math.max((400 * distance) / 100, 100)}%)\` 563 }, 564 { 565 duration: 0.4, 566 ease: 'power1', 567 scale: 1, 568 filter: 'brightness(100%) contrast(100%)' 569 }, 570 0 571 ) 572 .to( 573 img.DOM.el, 574 { 575 duration: 0.4, 576 ease: 'power3', 577 opacity: 0 578 }, 579 0.4 580 ) 581 .to( 582 img.DOM.el, 583 { 584 duration: 1.5, 585 ease: 'power4', 586 x: \`+=\${dx * 110}\`, 587 y: \`+=\${dy * 110}\` 588 }, 589 0.05 590 ); 591 } 592 593 destroy() { 594 this.destroyed = true; 595 if (this.rafId !== null) { 596 cancelAnimationFrame(this.rafId); 597 this.rafId = null; 598 } 599 this.container.removeEventListener('mousemove', this.handlePointerMove); 600 this.container.removeEventListener('touchmove', this.handlePointerMove); 601 this.container.removeEventListener('mousemove', this.initRender); 602 this.container.removeEventListener('touchmove', this.initRender);
603 this.images.forEach(img => { 604 gsap.killTweensOf(img.DOM.el); 605 img.destroy(); 606 }); 607 } 608 609 onImageActivated() { 610 this.activeImagesCount++; 611 this.isIdle = false; 612 } 613 onImageDeactivated() { 614 this.activeImagesCount--; 615 if (this.activeImagesCount === 0) this.isIdle = true; 616 } 617} 618 619class ImageTrailVariant5 { 620 constructor(container) { 621 this.container = container; 622 this.rafId = null; 623 this.destroyed = false; 624 this.DOM = { el: container }; 625 this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img)); 626 this.imagesTotal = this.images.length; 627 this.imgPosition = 0; 628 this.zIndexVal = 1; 629 this.activeImagesCount = 0; 630 this.isIdle = true; 631 this.threshold = 80; 632 633 this.mousePos = { x: 0, y: 0 }; 634 this.lastMousePos = { x: 0, y: 0 }; 635 this.cacheMousePos = { x: 0, y: 0 }; 636 this.lastAngle = 0; 637 638 const handlePointerMove = ev => { 639 const rect = container.getBoundingClientRect(); 640 this.mousePos = getLocalPointerPos(ev, rect); 641 }; 642 container.addEventListener('mousemove', handlePointerMove); 643 container.addEventListener('touchmove', handlePointerMove); 644 645 const initRender = ev => { 646 const rect = container.getBoundingClientRect(); 647 this.mousePos = getLocalPointerPos(ev, rect); 648 this.cacheMousePos = { ...this.mousePos }; 649 this.rafId = requestAnimationFrame(() => this.render()); 650 container.removeEventListener('mousemove', initRender); 651 container.removeEventListener('touchmove', initRender); 652 }; 653 container.addEventListener('mousemove', initRender); 654 container.addEventListener('touchmove', initRender); 655 this.handlePointerMove = handlePointerMove; 656 this.initRender = initRender; 657 } 658 659 render() { 660 if (this.destroyed) return; 661 662 let distance = getMouseDistance(this.mousePos, this.lastMousePos); 663 if (distance > this.threshold) { 664 this.showNextImage(); 665 this.lastMousePos = { ...this.mousePos }; 666 } 667 this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); 668 this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); 669 if (this.isIdle && this.zIndexVal !== 1) this.zIndexVal = 1; 670 this.rafId = requestAnimationFrame(() => this.render()); 671 } 672 673 showNextImage() { 674 let dx = this.mousePos.x - this.cacheMousePos.x; 675 let dy = this.mousePos.y - this.cacheMousePos.y; 676 let angle = Math.atan2(dy, dx) * (180 / Math.PI); 677 if (angle < 0) angle += 360; 678 if (angle > 90 && angle <= 270) angle += 180; 679 const isMovingClockwise = angle >= this.lastAngle; 680 this.lastAngle = angle; 681 let startAngle = isMovingClockwise ? angle - 10 : angle + 10; 682 let distance = Math.sqrt(dx * dx + dy * dy); 683 if (distance !== 0) { 684 dx /= distance; 685 dy /= distance; 686 } 687 dx *= distance / 150; 688 dy *= distance / 150; 689 690 ++this.zIndexVal; 691 this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0; 692 const img = this.images[this.imgPosition]; 693 gsap.killTweensOf(img.DOM.el); 694 695 gsap 696 .timeline({ 697 onStart: () => this.onImageActivated(), 698 onComplete: () => this.onImageDeactivated() 699 }) 700 .fromTo( 701 img.DOM.el, 702 { 703 opacity: 1, 704 filter: 'brightness(80%)', 705 scale: 0.1, 706 zIndex: this.zIndexVal, 707 x: this.cacheMousePos.x - img.rect.width / 2, 708 y: this.cacheMousePos.y - img.rect.height / 2, 709 rotation: startAngle 710 }, 711 { 712 duration: 1, 713 ease: 'power2', 714 scale: 1, 715 filter: 'brightness(100%)', 716 x: this.mousePos.x - img.rect.width / 2 + dx * 70, 717 y: this.mousePos.y - img.rect.height / 2 + dy * 70, 718 rotation: this.lastAngle 719 }, 720 0 721 ) 722 .to( 723 img.DOM.el, 724 { 725 duration: 0.4, 726 ease: 'expo', 727 opacity: 0 728 }, 729 0.5 730 ) 731 .to( 732 img.DOM.el, 733 { 734 duration: 1.5, 735 ease: 'power4', 736 x: \`+=\${dx * 120}\`, 737 y: \`+=\${dy * 120}\` 738 }, 739 0.05 740 ); 741 } 742 743 destroy() { 744 this.destroyed = true; 745 if (this.rafId !== null) { 746 cancelAnimationFrame(this.rafId); 747 this.rafId = null; 748 } 749 this.container.removeEventListener('mousemove', this.handlePointerMove); 750 this.container.removeEventListener('touchmove', this.handlePointerMove); 751 this.container.removeEventListener('mousemove', this.initRender); 752 this.container.removeEventListener('touchmove', this.initRender);
753 this.images.forEach(img => { 754 gsap.killTweensOf(img.DOM.el); 755 img.destroy(); 756 }); 757 } 758 759 onImageActivated() { 760 this.activeImagesCount++; 761 this.isIdle = false; 762 } 763 onImageDeactivated() { 764 this.activeImagesCount--; 765 if (this.activeImagesCount === 0) this.isIdle = true; 766 } 767} 768 769class ImageTrailVariant6 { 770 constructor(container) { 771 this.container = container; 772 this.rafId = null; 773 this.destroyed = false; 774 this.DOM = { el: container }; 775 this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img)); 776 this.imagesTotal = this.images.length; 777 this.imgPosition = 0; 778 this.zIndexVal = 1; 779 this.activeImagesCount = 0; 780 this.isIdle = true; 781 this.threshold = 80; 782 783 this.mousePos = { x: 0, y: 0 }; 784 this.lastMousePos = { x: 0, y: 0 }; 785 this.cacheMousePos = { x: 0, y: 0 }; 786 787 const handlePointerMove = ev => { 788 const rect = container.getBoundingClientRect(); 789 this.mousePos = getLocalPointerPos(ev, rect); 790 }; 791 container.addEventListener('mousemove', handlePointerMove); 792 container.addEventListener('touchmove', handlePointerMove); 793 794 const initRender = ev => { 795 const rect = container.getBoundingClientRect(); 796 this.mousePos = getLocalPointerPos(ev, rect); 797 this.cacheMousePos = { ...this.mousePos }; 798 this.rafId = requestAnimationFrame(() => this.render()); 799 container.removeEventListener('mousemove', initRender); 800 container.removeEventListener('touchmove', initRender); 801 }; 802 container.addEventListener('mousemove', initRender); 803 container.addEventListener('touchmove', initRender); 804 this.handlePointerMove = handlePointerMove; 805 this.initRender = initRender; 806 } 807 808 render() { 809 if (this.destroyed) return; 810 811 let distance = getMouseDistance(this.mousePos, this.lastMousePos); 812 this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.3); 813 this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.3); 814 815 if (distance > this.threshold) { 816 this.showNextImage(); 817 this.lastMousePos = { ...this.mousePos }; 818 } 819 if (this.isIdle && this.zIndexVal !== 1) { 820 this.zIndexVal = 1; 821 } 822 this.rafId = requestAnimationFrame(() => this.render()); 823 } 824 825 mapSpeedToSize(speed, minSize, maxSize) { 826 const maxSpeed = 200; 827 return minSize + (maxSize - minSize) * Math.min(speed / maxSpeed, 1); 828 } 829 mapSpeedToBrightness(speed, minB, maxB) { 830 const maxSpeed = 70; 831 return minB + (maxB - minB) * Math.min(speed / maxSpeed, 1); 832 } 833 mapSpeedToBlur(speed, minBlur, maxBlur) { 834 const maxSpeed = 90; 835 return minBlur + (maxBlur - minBlur) * Math.min(speed / maxSpeed, 1); 836 } 837 mapSpeedToGrayscale(speed, minG, maxG) { 838 const maxSpeed = 90; 839 return minG + (maxG - minG) * Math.min(speed / maxSpeed, 1); 840 } 841 842 showNextImage() { 843 let dx = this.mousePos.x - this.cacheMousePos.x; 844 let dy = this.mousePos.y - this.cacheMousePos.y; 845 let speed = Math.sqrt(dx * dx + dy * dy); 846 847 ++this.zIndexVal; 848 this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0; 849 const img = this.images[this.imgPosition]; 850 851 let scaleFactor = this.mapSpeedToSize(speed, 0.3, 2); 852 let brightnessValue = this.mapSpeedToBrightness(speed, 0, 1.3); 853 let blurValue = this.mapSpeedToBlur(speed, 20, 0); 854 let grayscaleValue = this.mapSpeedToGrayscale(speed, 600, 0); 855 856 gsap.killTweensOf(img.DOM.el); 857 gsap 858 .timeline({ 859 onStart: () => this.onImageActivated(), 860 onComplete: () => this.onImageDeactivated() 861 }) 862 .fromTo( 863 img.DOM.el, 864 { 865 opacity: 1, 866 scale: 0, 867 zIndex: this.zIndexVal, 868 x: this.cacheMousePos.x - img.rect.width / 2, 869 y: this.cacheMousePos.y - img.rect.height / 2 870 }, 871 { 872 duration: 0.8, 873 ease: 'power3', 874 scale: scaleFactor, 875 filter: \`grayscale(\${grayscaleValue * 100}%) brightness(\${brightnessValue * 100}%) blur(\${blurValue}px)\`, 876 x: this.mousePos.x - img.rect.width / 2, 877 y: this.mousePos.y - img.rect.height / 2 878 }, 879 0 880 ) 881 .fromTo( 882 img.DOM.inner, 883 { 884 scale: 2 885 }, 886 { 887 duration: 0.8, 888 ease: 'power3', 889 scale: 1 890 }, 891 0 892 ) 893 .to( 894 img.DOM.el, 895 { 896 duration: 0.4, 897 ease: 'power3.in', 898 opacity: 0, 899 scale: 0.2 900 }, 901 0.45 902 ); 903 } 904 905 destroy() { 906 this.destroyed = true; 907 if (this.rafId !== null) { 908 cancelAnimationFrame(this.rafId); 909 this.rafId = null; 910 } 911 this.container.removeEventListener('mousemove', this.handlePointerMove); 912 this.container.removeEventListener('touchmove', this.handlePointerMove); 913 this.container.removeEventListener('mousemove', this.initRender); 914 this.container.removeEventListener('touchmove', this.initRender);
915 this.images.forEach(img => { 916 gsap.killTweensOf(img.DOM.el); 917 img.destroy(); 918 }); 919 } 920 921 onImageActivated() { 922 this.activeImagesCount++; 923 this.isIdle = false; 924 } 925 onImageDeactivated() { 926 this.activeImagesCount--; 927 if (this.activeImagesCount === 0) { 928 this.isIdle = true; 929 } 930 } 931} 932 933function getNewPosition(position, offset, arr) { 934 const realOffset = Math.abs(offset) % arr.length; 935 if (position - realOffset >= 0) { 936 return position - realOffset; 937 } else { 938 return arr.length - (realOffset - position); 939 } 940} 941class ImageTrailVariant7 { 942 constructor(container) { 943 this.container = container; 944 this.rafId = null; 945 this.destroyed = false; 946 this.DOM = { el: container }; 947 this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img)); 948 this.imagesTotal = this.images.length; 949 this.imgPosition = 0; 950 this.zIndexVal = 1; 951 this.activeImagesCount = 0; 952 this.isIdle = true; 953 this.threshold = 80; 954 955 this.mousePos = { x: 0, y: 0 }; 956 this.lastMousePos = { x: 0, y: 0 }; 957 this.cacheMousePos = { x: 0, y: 0 }; 958 959 this.visibleImagesCount = 0; 960 this.visibleImagesTotal = 9; 961 this.visibleImagesTotal = Math.min(this.visibleImagesTotal, this.imagesTotal - 1); 962 963 const handlePointerMove = ev => { 964 const rect = container.getBoundingClientRect(); 965 this.mousePos = getLocalPointerPos(ev, rect); 966 }; 967 container.addEventListener('mousemove', handlePointerMove); 968 container.addEventListener('touchmove', handlePointerMove); 969 970 const initRender = ev => { 971 const rect = container.getBoundingClientRect(); 972 this.mousePos = getLocalPointerPos(ev, rect); 973 this.cacheMousePos = { ...this.mousePos }; 974 this.rafId = requestAnimationFrame(() => this.render()); 975 container.removeEventListener('mousemove', initRender); 976 container.removeEventListener('touchmove', initRender); 977 }; 978 container.addEventListener('mousemove', initRender); 979 container.addEventListener('touchmove', initRender); 980 this.handlePointerMove = handlePointerMove; 981 this.initRender = initRender; 982 } 983 984 render() { 985 if (this.destroyed) return; 986 987 let distance = getMouseDistance(this.mousePos, this.lastMousePos); 988 this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.3); 989 this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.3); 990 991 if (distance > this.threshold) { 992 this.showNextImage(); 993 this.lastMousePos = { ...this.mousePos }; 994 } 995 if (this.isIdle && this.zIndexVal !== 1) this.zIndexVal = 1; 996 997 this.rafId = requestAnimationFrame(() => this.render()); 998 } 999 1000 showNextImage() { 1001 ++this.zIndexVal; 1002 this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0; 1003 const img = this.images[this.imgPosition]; 1004 ++this.visibleImagesCount; 1005 1006 gsap.killTweensOf(img.DOM.el); 1007 const scaleValue = gsap.utils.random(0.5, 1.6); 1008 1009 gsap 1010 .timeline({ 1011 onStart: () => this.onImageActivated(), 1012 onComplete: () => this.onImageDeactivated() 1013 }) 1014 .fromTo( 1015 img.DOM.el, 1016 { 1017 scale: scaleValue - Math.max(gsap.utils.random(0.2, 0.6), 0), 1018 rotationZ: 0, 1019 opacity: 1, 1020 zIndex: this.zIndexVal, 1021 x: this.cacheMousePos.x - img.rect.width / 2, 1022 y: this.cacheMousePos.y - img.rect.height / 2 1023 }, 1024 { 1025 duration: 0.4, 1026 ease: 'power3', 1027 scale: scaleValue, 1028 rotationZ: gsap.utils.random(-3, 3), 1029 x: this.mousePos.x - img.rect.width / 2, 1030 y: this.mousePos.y - img.rect.height / 2 1031 }, 1032 0 1033 ); 1034 1035 if (this.visibleImagesCount >= this.visibleImagesTotal) { 1036 const lastInQueue = getNewPosition(this.imgPosition, this.visibleImagesTotal, this.images); 1037 const oldImg = this.images[lastInQueue]; 1038 gsap.to(oldImg.DOM.el, { 1039 duration: 0.4, 1040 ease: 'power4', 1041 opacity: 0, 1042 scale: 1.3, 1043 onComplete: () => { 1044 if (this.activeImagesCount === 0) { 1045 this.isIdle = true; 1046 } 1047 } 1048 }); 1049 } 1050 } 1051 1052 destroy() { 1053 this.destroyed = true; 1054 if (this.rafId !== null) { 1055 cancelAnimationFrame(this.rafId); 1056 this.rafId = null; 1057 } 1058 this.container.removeEventListener('mousemove', this.handlePointerMove); 1059 this.container.removeEventListener('touchmove', this.handlePointerMove); 1060 this.container.removeEventListener('mousemove', this.initRender); 1061 this.container.removeEventListener('touchmove', this.initRender);
1062 this.images.forEach(img => { 1063 gsap.killTweensOf(img.DOM.el); 1064 img.destroy(); 1065 }); 1066 } 1067 1068 onImageActivated() { 1069 this.activeImagesCount++; 1070 this.isIdle = false; 1071 } 1072 onImageDeactivated() { 1073 this.activeImagesCount--; 1074 } 1075} 1076 1077class ImageTrailVariant8 { 1078 constructor(container) { 1079 this.container = container; 1080 this.rafId = null; 1081 this.destroyed = false; 1082 this.DOM = { el: container }; 1083 this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img)); 1084 this.imagesTotal = this.images.length; 1085 this.imgPosition = 0; 1086 this.zIndexVal = 1; 1087 this.activeImagesCount = 0; 1088 this.isIdle = true; 1089 this.threshold = 80; 1090 1091 this.mousePos = { x: 0, y: 0 }; 1092 this.lastMousePos = { x: 0, y: 0 }; 1093 this.cacheMousePos = { x: 0, y: 0 }; 1094 1095 this.rotation = { x: 0, y: 0 }; 1096 this.cachedRotation = { x: 0, y: 0 }; 1097 this.zValue = 0; 1098 this.cachedZValue = 0; 1099 1100 const handlePointerMove = ev => { 1101 const rect = container.getBoundingClientRect(); 1102 this.mousePos = getLocalPointerPos(ev, rect); 1103 }; 1104 container.addEventListener('mousemove', handlePointerMove); 1105 container.addEventListener('touchmove', handlePointerMove); 1106 1107 const initRender = ev => { 1108 const rect = container.getBoundingClientRect(); 1109 this.mousePos = getLocalPointerPos(ev, rect); 1110 this.cacheMousePos = { ...this.mousePos }; 1111 this.rafId = requestAnimationFrame(() => this.render()); 1112 container.removeEventListener('mousemove', initRender); 1113 container.removeEventListener('touchmove', initRender); 1114 }; 1115 container.addEventListener('mousemove', initRender); 1116 container.addEventListener('touchmove', initRender); 1117 this.handlePointerMove = handlePointerMove; 1118 this.initRender = initRender; 1119 } 1120 1121 render() { 1122 if (this.destroyed) return; 1123 1124 let distance = getMouseDistance(this.mousePos, this.lastMousePos); 1125 this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); 1126 this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); 1127 1128 if (distance > this.threshold) { 1129 this.showNextImage(); 1130 this.lastMousePos = { ...this.mousePos }; 1131 } 1132 if (this.isIdle && this.zIndexVal !== 1) { 1133 this.zIndexVal = 1; 1134 } 1135 this.rafId = requestAnimationFrame(() => this.render()); 1136 } 1137 1138 showNextImage() { 1139 const rect = this.container.getBoundingClientRect(); 1140 const centerX = rect.width / 2; 1141 const centerY = rect.height / 2; 1142 const relX = this.mousePos.x - centerX; 1143 const relY = this.mousePos.y - centerY; 1144 1145 this.rotation.x = -(relY / centerY) * 30; 1146 this.rotation.y = (relX / centerX) * 30; 1147 this.cachedRotation = { ...this.rotation }; 1148 1149 const distanceFromCenter = Math.sqrt(relX * relX + relY * relY); 1150 const maxDistance = Math.sqrt(centerX * centerX + centerY * centerY); 1151 const proportion = distanceFromCenter / maxDistance; 1152 this.zValue = proportion * 1200 - 600; 1153 this.cachedZValue = this.zValue; 1154 const normalizedZ = (this.zValue + 600) / 1200; 1155 const brightness = 0.2 + normalizedZ * 2.3; 1156 1157 ++this.zIndexVal; 1158 this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0; 1159 const img = this.images[this.imgPosition]; 1160 gsap.killTweensOf(img.DOM.el); 1161 1162 gsap 1163 .timeline({ 1164 onStart: () => this.onImageActivated(), 1165 onComplete: () => this.onImageDeactivated() 1166 }) 1167 .set(this.DOM.el, { perspective: 1000 }, 0) 1168 .fromTo( 1169 img.DOM.el, 1170 { 1171 opacity: 1, 1172 z: 0, 1173 scale: 1 + this.cachedZValue / 1000, 1174 zIndex: this.zIndexVal, 1175 x: this.cacheMousePos.x - img.rect.width / 2, 1176 y: this.cacheMousePos.y - img.rect.height / 2, 1177 rotationX: this.cachedRotation.x, 1178 rotationY: this.cachedRotation.y, 1179 filter: \`brightness(\${brightness})\` 1180 }, 1181 { 1182 duration: 1, 1183 ease: 'expo', 1184 scale: 1 + this.zValue / 1000, 1185 x: this.mousePos.x - img.rect.width / 2, 1186 y: this.mousePos.y - img.rect.height / 2, 1187 rotationX: this.rotation.x, 1188 rotationY: this.rotation.y 1189 }, 1190 0 1191 ) 1192 .to( 1193 img.DOM.el, 1194 { 1195 duration: 0.4, 1196 ease: 'power2', 1197 opacity: 0,
1198 z: -800 1199 }, 1200 0.3 1201 ); 1202 } 1203 1204 destroy() { 1205 this.destroyed = true; 1206 if (this.rafId !== null) { 1207 cancelAnimationFrame(this.rafId); 1208 this.rafId = null; 1209 } 1210 this.container.removeEventListener('mousemove', this.handlePointerMove); 1211 this.container.removeEventListener('touchmove', this.handlePointerMove); 1212 this.container.removeEventListener('mousemove', this.initRender); 1213 this.container.removeEventListener('touchmove', this.initRender); 1214 this.images.forEach(img => { 1215 gsap.killTweensOf(img.DOM.el); 1216 img.destroy(); 1217 }); 1218 } 1219 1220 onImageActivated() { 1221 this.activeImagesCount++; 1222 this.isIdle = false; 1223 } 1224 onImageDeactivated() { 1225 this.activeImagesCount--; 1226 if (this.activeImagesCount === 0) this.isIdle = true; 1227 } 1228} 1229 1230const variantMap = { 1231 1: ImageTrailVariant1, 1232 2: ImageTrailVariant2, 1233 3: ImageTrailVariant3, 1234 4: ImageTrailVariant4, 1235 5: ImageTrailVariant5, 1236 6: ImageTrailVariant6, 1237 7: ImageTrailVariant7, 1238 8: ImageTrailVariant8 1239}; 1240 1241export default function ImageTrail({ items = [], variant = 1 }) { 1242 const containerRef = useRef(null); 1243 1244 useEffect(() => { 1245 if (!containerRef.current) return; 1246 1247 const Cls = variantMap[variant] || variantMap[1]; 1248 const instance = new Cls(containerRef.current); 1249 1250 return () => { 1251 instance.destroy(); 1252 }; 1253 }, [variant, items]); 1254 1255 return ( 1256 <div className="content" ref={containerRef}> 1257 {items.map((url, i) => ( 1258 <div className="content__img" key={i}> 1259 <div className="content__img-inner" style={{ backgroundImage: \`url(\${url})\` }} /> 1260 </div> 1261 ))} 1262 </div> 1263 ); 1264} 1265`,w=`.content { 1266 width: 100%; 1267 height: 100%; 1268 position: relative; 1269 z-index: 100; 1270 border-radius: 8px; 1271 background: transparent; 1272 overflow: visible; 1273} 1274 1275.content__img { 1276 width: 190px; 1277 aspect-ratio: 1.1; 1278 border-radius: 15px; 1279 position: absolute; 1280 top: 0; 1281 left: 0; 1282 opacity: 0; 1283 overflow: hidden; 1284 will-change: transform, filter; 1285} 1286 1287.content__img-inner { 1288 background-position: 50% 50%; 1289 width: calc(100% + 20px); 1290 height: calc(100% + 20px); 1291 background-size: cover; 1292 position: absolute; 1293 top: calc(-1 * 20px / 2); 1294 left: calc(-1 * 20px / 2); 1295} 1296`,V=`'use client'; 1297 1298import { useRef, useEffect } from 'react'; 1299import { gsap } from 'gsap'; 1300 1301function lerp(a, b, n) { 1302 return (1 - n) * a + n * b; 1303} 1304 1305function getLocalPointerPos(e, rect) { 1306 let clientX = 0, 1307 clientY = 0; 1308 if (e.touches && e.touches.length > 0) { 1309 clientX = e.touches[0].clientX; 1310 clientY = e.touches[0].clientY; 1311 } else { 1312 clientX = e.clientX; 1313 clientY = e.clientY; 1314 } 1315 return { 1316 x: clientX - rect.left, 1317 y: clientY - rect.top 1318 }; 1319} 1320function getMouseDistance(p1, p2) { 1321 const dx = p1.x - p2.x; 1322 const dy = p1.y - p2.y; 1323 return Math.hypot(dx, dy); 1324} 1325 1326class ImageItem { 1327 DOM = { el: null, inner: null }; 1328 defaultStyle = { scale: 1, x: 0, y: 0, opacity: 0 }; 1329 rect = null; 1330 1331 constructor(DOM_el) { 1332 this.DOM.el = DOM_el; 1333 this.DOM.inner = this.DOM.el.querySelector('.content__img-inner'); 1334 this.getRect(); 1335 this.initEvents(); 1336 } 1337 initEvents() { 1338 this.resize = () => { 1339 gsap.set(this.DOM.el, this.defaultStyle); 1340 this.getRect(); 1341 }; 1342 window.addEventListener('resize', this.resize); 1343 } 1344 getRect() { 1345 this.rect = this.DOM.el.getBoundingClientRect(); 1346 } 1347 1348 destroy() { 1349 window.removeEventListener('resize', this.resize); 1350 } 1351} 1352 1353class ImageTrailVariant1 { 1354 constructor(container) { 1355 this.container = container; 1356 this.rafId = null; 1357 this.destroyed = false; 1358 this.DOM = { el: container }; 1359 this.images = [...this.DOM.el.querySelectorAll('.content__img')].map(img => new ImageItem(img)); 1360 this.imagesTotal = this.images.length; 1361 this.imgPosition = 0; 1362 this.zIndexVal = 1; 1363 this.activeImagesCount = 0; 1364 this.isIdle = true; 1365 this.threshold = 80; 1366 1367 this.mousePos = { x: 0, y: 0 }; 1368 this.lastMousePos = { x: 0, y: 0 }; 1369 this.cacheMousePos = { x: 0, y: 0 }; 1370 1371 const handlePointerMove = ev => { 1372 const rect = this.container.getBoundingClientRect(); 1373 this.mousePos = getLocalPointerPos(ev, rect); 1374 }; 1375 container.addEventListener('mousemove', handlePointerMove); 1376 container.addEventListener('touchmove', handlePointerMove); 1377 1378 const initRender = ev => { 1379 const rect = this.container.getBoundingClientRect(); 1380 this.mousePos = getLocalPointerPos(ev, rect); 1381 this.cacheMousePos = { ...this.mousePos }; 1382 1383 this.rafId = requestAnimationFrame(() => this.render()); 1384 1385 container.removeEventListener('mousemove', initRender); 1386 container.removeEventListener('touchmove', initRender); 1387 }; 1388 container.addEventListener('mousemove', initRender); 1389 container.addEventListener('touchmove', initRender); 1390 this.handlePointerMove = handlePointerMove; 1391 this.initRender = initRender; 1392 } 1393 1394 render() { 1395 if (this.destroyed) return; 1396 1397 let distance = getMouseDistance(this.mousePos, this.lastMousePos); 1398 this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); 1399 this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); 1400 1401 if (distance > this.threshold) { 1402 this.showNextImage(); 1403 this.lastMousePos = { ...this.mousePos }; 1404 } 1405 if (this.isIdle && this.zIndexVal !== 1) { 1406 this.zIndexVal = 1; 1407 } 1408 this.rafId = requestAnimationFrame(() => this.render()); 1409 } 1410 1411 showNextImage() { 1412 ++this.zIndexVal; 1413 this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0; 1414 const img = this.images[this.imgPosition]; 1415 1416 gsap.killTweensOf(img.DOM.el); 1417 gsap
1418 .timeline({ 1419 onStart: () => this.onImageActivated(), 1420 onComplete: () => this.onImageDeactivated() 1421 }) 1422 .fromTo( 1423 img.DOM.el, 1424 { 1425 opacity: 1, 1426 scale: 1, 1427 zIndex: this.zIndexVal, 1428 x: this.cacheMousePos.x - img.rect.width / 2, 1429 y: this.cacheMousePos.y - img.rect.height / 2 1430 }, 1431 { 1432 duration: 0.4, 1433 ease: 'power1', 1434 x: this.mousePos.x - img.rect.width / 2, 1435 y: this.mousePos.y - img.rect.height / 2 1436 }, 1437 0 1438 ) 1439 .to( 1440 img.DOM.el, 1441 { 1442 duration: 0.4, 1443 ease: 'power3', 1444 opacity: 0, 1445 scale: 0.2 1446 }, 1447 0.4 1448 ); 1449 } 1450 1451 destroy() { 1452 this.destroyed = true; 1453 if (this.rafId !== null) { 1454 cancelAnimationFrame(this.rafId); 1455 this.rafId = null; 1456 } 1457 this.container.removeEventListener('mousemove', this.handlePointerMove); 1458 this.container.removeEventListener('touchmove', this.handlePointerMove); 1459 this.container.removeEventListener('mousemove', this.initRender); 1460 this.container.removeEventListener('touchmove', this.initRender); 1461 this.images.forEach(img => { 1462 gsap.killTweensOf(img.DOM.el); 1463 img.destroy(); 1464 }); 1465 } 1466 1467 onImageActivated() { 1468 this.activeImagesCount++; 1469 this.isIdle = false; 1470 } 1471 onImageDeactivated() { 1472 this.activeImagesCount--; 1473 if (this.activeImagesCount === 0) { 1474 this.isIdle = true; 1475 } 1476 } 1477} 1478 1479class ImageTrailVariant2 { 1480 constructor(container) { 1481 this.container = container; 1482 this.rafId = null; 1483 this.destroyed = false; 1484 this.DOM = { el: container }; 1485 this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img)); 1486 this.imagesTotal = this.images.length; 1487 this.imgPosition = 0; 1488 this.zIndexVal = 1; 1489 this.activeImagesCount = 0; 1490 this.isIdle = true; 1491 this.threshold = 80; 1492 1493 this.mousePos = { x: 0, y: 0 }; 1494 this.lastMousePos = { x: 0, y: 0 }; 1495 this.cacheMousePos = { x: 0, y: 0 }; 1496 1497 const handlePointerMove = ev => { 1498 const rect = container.getBoundingClientRect(); 1499 this.mousePos = getLocalPointerPos(ev, rect); 1500 }; 1501 container.addEventListener('mousemove', handlePointerMove); 1502 container.addEventListener('touchmove', handlePointerMove); 1503 1504 const initRender = ev => { 1505 const rect = container.getBoundingClientRect(); 1506 this.mousePos = getLocalPointerPos(ev, rect); 1507 this.cacheMousePos = { ...this.mousePos }; 1508 1509 this.rafId = requestAnimationFrame(() => this.render()); 1510 1511 container.removeEventListener('mousemove', initRender); 1512 container.removeEventListener('touchmove', initRender); 1513 }; 1514 container.addEventListener('mousemove', initRender); 1515 container.addEventListener('touchmove', initRender); 1516 this.handlePointerMove = handlePointerMove; 1517 this.initRender = initRender; 1518 } 1519 1520 render() { 1521 if (this.destroyed) return; 1522 1523 let distance = getMouseDistance(this.mousePos, this.lastMousePos); 1524 this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); 1525 this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); 1526 1527 if (distance > this.threshold) { 1528 this.showNextImage(); 1529 this.lastMousePos = { ...this.mousePos }; 1530 } 1531 if (this.isIdle && this.zIndexVal !== 1) { 1532 this.zIndexVal = 1; 1533 } 1534 this.rafId = requestAnimationFrame(() => this.render()); 1535 } 1536 1537 showNextImage() { 1538 ++this.zIndexVal; 1539 this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0; 1540 const img = this.images[this.imgPosition]; 1541 1542 gsap.killTweensOf(img.DOM.el); 1543 gsap 1544 .timeline({ 1545 onStart: () => this.onImageActivated(), 1546 onComplete: () => this.onImageDeactivated() 1547 }) 1548 .fromTo( 1549 img.DOM.el, 1550 { 1551 opacity: 1, 1552 scale: 0, 1553 zIndex: this.zIndexVal, 1554 x: this.cacheMousePos.x - img.rect.width / 2, 1555 y: this.cacheMousePos.y - img.rect.height / 2 1556 }, 1557 { 1558 duration: 0.4, 1559 ease: 'power1', 1560 scale: 1, 1561 x: this.mousePos.x - img.rect.width / 2, 1562 y: this.mousePos.y - img.rect.height / 2 1563 }, 1564 0 1565 ) 1566 .fromTo( 1567 img.DOM.inner, 1568 { 1569 scale: 2.8, 1570 filter: 'brightness(250%)' 1571 }, 1572 { 1573 duration: 0.4, 1574 ease: 'power1', 1575 scale: 1, 1576 filter: 'brightness(100%)' 1577 }, 1578 0 1579 ) 1580 .to( 1581 img.DOM.el, 1582 { 1583 duration: 0.4, 1584 ease: 'power2', 1585 opacity: 0, 1586 scale: 0.2 1587 }, 1588 0.45 1589 ); 1590 } 1591 1592 destroy() { 1593 this.destroyed = true; 1594 if (this.rafId !== null) { 1595 cancelAnimationFrame(this.rafId); 1596 this.rafId = null; 1597 } 1598 this.container.removeEventListener('mousemove', this.handlePointerMove); 1599 this.container.removeEventListener('touchmove', this.handlePointerMove); 1600 this.container.removeEventListener('mousemove', this.initRender); 1601 this.container.removeEventListener('touchmove', this.initRender);
1602 this.images.forEach(img => { 1603 gsap.killTweensOf(img.DOM.el); 1604 img.destroy(); 1605 }); 1606 } 1607 1608 onImageActivated() { 1609 this.activeImagesCount++; 1610 this.isIdle = false; 1611 } 1612 onImageDeactivated() { 1613 this.activeImagesCount--; 1614 if (this.activeImagesCount === 0) this.isIdle = true; 1615 } 1616} 1617 1618class ImageTrailVariant3 { 1619 constructor(container) { 1620 this.container = container; 1621 this.rafId = null; 1622 this.destroyed = false; 1623 this.DOM = { el: container }; 1624 this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img)); 1625 this.imagesTotal = this.images.length; 1626 this.imgPosition = 0; 1627 this.zIndexVal = 1; 1628 this.activeImagesCount = 0; 1629 this.isIdle = true; 1630 this.threshold = 80; 1631 1632 this.mousePos = { x: 0, y: 0 }; 1633 this.lastMousePos = { x: 0, y: 0 }; 1634 this.cacheMousePos = { x: 0, y: 0 }; 1635 1636 const handlePointerMove = ev => { 1637 const rect = container.getBoundingClientRect(); 1638 this.mousePos = getLocalPointerPos(ev, rect); 1639 }; 1640 container.addEventListener('mousemove', handlePointerMove); 1641 container.addEventListener('touchmove', handlePointerMove); 1642 1643 const initRender = ev => { 1644 const rect = container.getBoundingClientRect(); 1645 this.mousePos = getLocalPointerPos(ev, rect); 1646 this.cacheMousePos = { ...this.mousePos }; 1647 1648 this.rafId = requestAnimationFrame(() => this.render()); 1649 container.removeEventListener('mousemove', initRender); 1650 container.removeEventListener('touchmove', initRender); 1651 }; 1652 container.addEventListener('mousemove', initRender); 1653 container.addEventListener('touchmove', initRender); 1654 this.handlePointerMove = handlePointerMove; 1655 this.initRender = initRender; 1656 } 1657 1658 render() { 1659 if (this.destroyed) return; 1660 1661 let distance = getMouseDistance(this.mousePos, this.lastMousePos); 1662 this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); 1663 this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); 1664 1665 if (distance > this.threshold) { 1666 this.showNextImage(); 1667 this.lastMousePos = { ...this.mousePos }; 1668 } 1669 if (this.isIdle && this.zIndexVal !== 1) { 1670 this.zIndexVal = 1; 1671 } 1672 this.rafId = requestAnimationFrame(() => this.render()); 1673 } 1674 1675 showNextImage() { 1676 ++this.zIndexVal; 1677 this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0; 1678 const img = this.images[this.imgPosition]; 1679 gsap.killTweensOf(img.DOM.el); 1680 1681 gsap 1682 .timeline({ 1683 onStart: () => this.onImageActivated(), 1684 onComplete: () => this.onImageDeactivated() 1685 }) 1686 .fromTo( 1687 img.DOM.el, 1688 { 1689 opacity: 1, 1690 scale: 0, 1691 zIndex: this.zIndexVal, 1692 xPercent: 0, 1693 yPercent: 0, 1694 x: this.cacheMousePos.x - img.rect.width / 2, 1695 y: this.cacheMousePos.y - img.rect.height / 2 1696 }, 1697 { 1698 duration: 0.4, 1699 ease: 'power1', 1700 scale: 1, 1701 x: this.mousePos.x - img.rect.width / 2, 1702 y: this.mousePos.y - img.rect.height / 2 1703 }, 1704 0 1705 ) 1706 .fromTo( 1707 img.DOM.inner, 1708 { 1709 scale: 1.2 1710 }, 1711 { 1712 duration: 0.4, 1713 ease: 'power1', 1714 scale: 1 1715 }, 1716 0 1717 ) 1718 .to( 1719 img.DOM.el, 1720 { 1721 duration: 0.6, 1722 ease: 'power2', 1723 opacity: 0, 1724 scale: 0.2, 1725 xPercent: () => gsap.utils.random(-30, 30), 1726 yPercent: -200 1727 }, 1728 0.6 1729 ); 1730 } 1731 1732 destroy() { 1733 this.destroyed = true; 1734 if (this.rafId !== null) { 1735 cancelAnimationFrame(this.rafId); 1736 this.rafId = null; 1737 } 1738 this.container.removeEventListener('mousemove', this.handlePointerMove); 1739 this.container.removeEventListener('touchmove', this.handlePointerMove); 1740 this.container.removeEventListener('mousemove', this.initRender); 1741 this.container.removeEventListener('touchmove', this.initRender); 1742 this.images.forEach(img => { 1743 gsap.killTweensOf(img.DOM.el); 1744 img.destroy(); 1745 }); 1746 } 1747 1748 onImageActivated() { 1749 this.activeImagesCount++; 1750 this.isIdle = false;
1751 } 1752 onImageDeactivated() { 1753 this.activeImagesCount--; 1754 if (this.activeImagesCount === 0) this.isIdle = true; 1755 } 1756} 1757 1758class ImageTrailVariant4 { 1759 constructor(container) { 1760 this.container = container; 1761 this.rafId = null; 1762 this.destroyed = false; 1763 this.DOM = { el: container }; 1764 this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img)); 1765 this.imagesTotal = this.images.length; 1766 this.imgPosition = 0; 1767 this.zIndexVal = 1; 1768 this.activeImagesCount = 0; 1769 this.isIdle = true; 1770 this.threshold = 80; 1771 1772 this.mousePos = { x: 0, y: 0 }; 1773 this.lastMousePos = { x: 0, y: 0 }; 1774 this.cacheMousePos = { x: 0, y: 0 }; 1775 1776 const handlePointerMove = ev => { 1777 const rect = container.getBoundingClientRect(); 1778 this.mousePos = getLocalPointerPos(ev, rect); 1779 }; 1780 container.addEventListener('mousemove', handlePointerMove); 1781 container.addEventListener('touchmove', handlePointerMove); 1782 1783 const initRender = ev => { 1784 const rect = container.getBoundingClientRect(); 1785 this.mousePos = getLocalPointerPos(ev, rect); 1786 this.cacheMousePos = { ...this.mousePos }; 1787 this.rafId = requestAnimationFrame(() => this.render()); 1788 container.removeEventListener('mousemove', initRender); 1789 container.removeEventListener('touchmove', initRender); 1790 }; 1791 container.addEventListener('mousemove', initRender); 1792 container.addEventListener('touchmove', initRender); 1793 this.handlePointerMove = handlePointerMove; 1794 this.initRender = initRender; 1795 } 1796 1797 render() { 1798 if (this.destroyed) return; 1799 1800 let distance = getMouseDistance(this.mousePos, this.lastMousePos); 1801 if (distance > this.threshold) { 1802 this.showNextImage(); 1803 this.lastMousePos = { ...this.mousePos }; 1804 } 1805 this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); 1806 this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); 1807 1808 if (this.isIdle && this.zIndexVal !== 1) this.zIndexVal = 1; 1809 this.rafId = requestAnimationFrame(() => this.render()); 1810 } 1811 1812 showNextImage() { 1813 ++this.zIndexVal; 1814 this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0; 1815 const img = this.images[this.imgPosition]; 1816 gsap.killTweensOf(img.DOM.el); 1817 1818 let dx = this.mousePos.x - this.cacheMousePos.x; 1819 let dy = this.mousePos.y - this.cacheMousePos.y; 1820 let distance = Math.sqrt(dx * dx + dy * dy); 1821 if (distance !== 0) { 1822 dx /= distance; 1823 dy /= distance; 1824 } 1825 dx *= distance / 100; 1826 dy *= distance / 100; 1827 1828 gsap 1829 .timeline({ 1830 onStart: () => this.onImageActivated(), 1831 onComplete: () => this.onImageDeactivated() 1832 }) 1833 .fromTo( 1834 img.DOM.el, 1835 { 1836 opacity: 1, 1837 scale: 0, 1838 zIndex: this.zIndexVal, 1839 x: this.cacheMousePos.x - img.rect.width / 2, 1840 y: this.cacheMousePos.y - img.rect.height / 2 1841 }, 1842 { 1843 duration: 0.4, 1844 ease: 'power1', 1845 scale: 1, 1846 x: this.mousePos.x - img.rect.width / 2, 1847 y: this.mousePos.y - img.rect.height / 2 1848 }, 1849 0 1850 ) 1851 .fromTo( 1852 img.DOM.inner, 1853 { 1854 scale: 2, 1855 filter: \`brightness(\${Math.max((400 * distance) / 100, 100)}%) contrast(\${Math.max((400 * distance) / 100, 100)}%)\` 1856 }, 1857 { 1858 duration: 0.4, 1859 ease: 'power1', 1860 scale: 1, 1861 filter: 'brightness(100%) contrast(100%)' 1862 }, 1863 0 1864 ) 1865 .to( 1866 img.DOM.el, 1867 { 1868 duration: 0.4, 1869 ease: 'power3', 1870 opacity: 0 1871 }, 1872 0.4 1873 ) 1874 .to( 1875 img.DOM.el, 1876 { 1877 duration: 1.5, 1878 ease: 'power4', 1879 x: \`+=\${dx * 110}\`, 1880 y: \`+=\${dy * 110}\` 1881 }, 1882 0.05 1883 ); 1884 } 1885 1886 destroy() { 1887 this.destroyed = true; 1888 if (this.rafId !== null) { 1889 cancelAnimationFrame(this.rafId); 1890 this.rafId = null; 1891 } 1892 this.container.removeEventListener('mousemove', this.handlePointerMove); 1893 this.container.removeEventListener('touchmove', this.handlePointerMove); 1894 this.container.removeEventListener('mousemove', this.initRender); 1895 this.container.removeEventListener('touchmove', this.initRender);
1896 this.images.forEach(img => { 1897 gsap.killTweensOf(img.DOM.el); 1898 img.destroy(); 1899 }); 1900 } 1901 1902 onImageActivated() { 1903 this.activeImagesCount++; 1904 this.isIdle = false; 1905 } 1906 onImageDeactivated() { 1907 this.activeImagesCount--; 1908 if (this.activeImagesCount === 0) this.isIdle = true; 1909 } 1910} 1911 1912class ImageTrailVariant5 { 1913 constructor(container) { 1914 this.container = container; 1915 this.rafId = null; 1916 this.destroyed = false; 1917 this.DOM = { el: container }; 1918 this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img)); 1919 this.imagesTotal = this.images.length; 1920 this.imgPosition = 0; 1921 this.zIndexVal = 1; 1922 this.activeImagesCount = 0; 1923 this.isIdle = true; 1924 this.threshold = 80; 1925 1926 this.mousePos = { x: 0, y: 0 }; 1927 this.lastMousePos = { x: 0, y: 0 }; 1928 this.cacheMousePos = { x: 0, y: 0 }; 1929 this.lastAngle = 0; 1930 1931 const handlePointerMove = ev => { 1932 const rect = container.getBoundingClientRect(); 1933 this.mousePos = getLocalPointerPos(ev, rect); 1934 }; 1935 container.addEventListener('mousemove', handlePointerMove); 1936 container.addEventListener('touchmove', handlePointerMove); 1937 1938 const initRender = ev => { 1939 const rect = container.getBoundingClientRect(); 1940 this.mousePos = getLocalPointerPos(ev, rect); 1941 this.cacheMousePos = { ...this.mousePos }; 1942 this.rafId = requestAnimationFrame(() => this.render()); 1943 container.removeEventListener('mousemove', initRender); 1944 container.removeEventListener('touchmove', initRender); 1945 }; 1946 container.addEventListener('mousemove', initRender); 1947 container.addEventListener('touchmove', initRender); 1948 this.handlePointerMove = handlePointerMove; 1949 this.initRender = initRender; 1950 } 1951 1952 render() { 1953 if (this.destroyed) return; 1954 1955 let distance = getMouseDistance(this.mousePos, this.lastMousePos); 1956 if (distance > this.threshold) { 1957 this.showNextImage(); 1958 this.lastMousePos = { ...this.mousePos }; 1959 } 1960 this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); 1961 this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); 1962 if (this.isIdle && this.zIndexVal !== 1) this.zIndexVal = 1; 1963 this.rafId = requestAnimationFrame(() => this.render()); 1964 } 1965 1966 showNextImage() { 1967 let dx = this.mousePos.x - this.cacheMousePos.x; 1968 let dy = this.mousePos.y - this.cacheMousePos.y; 1969 let angle = Math.atan2(dy, dx) * (180 / Math.PI); 1970 if (angle < 0) angle += 360; 1971 if (angle > 90 && angle <= 270) angle += 180; 1972 const isMovingClockwise = angle >= this.lastAngle; 1973 this.lastAngle = angle; 1974 let startAngle = isMovingClockwise ? angle - 10 : angle + 10; 1975 let distance = Math.sqrt(dx * dx + dy * dy); 1976 if (distance !== 0) { 1977 dx /= distance; 1978 dy /= distance; 1979 } 1980 dx *= distance / 150; 1981 dy *= distance / 150; 1982 1983 ++this.zIndexVal; 1984 this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0; 1985 const img = this.images[this.imgPosition]; 1986 gsap.killTweensOf(img.DOM.el); 1987 1988 gsap 1989 .timeline({ 1990 onStart: () => this.onImageActivated(), 1991 onComplete: () => this.onImageDeactivated() 1992 }) 1993 .fromTo( 1994 img.DOM.el, 1995 { 1996 opacity: 1, 1997 filter: 'brightness(80%)', 1998 scale: 0.1, 1999 zIndex: this.zIndexVal, 2000 x: this.cacheMousePos.x - img.rect.width / 2, 2001 y: this.cacheMousePos.y - img.rect.height / 2, 2002 rotation: startAngle 2003 }, 2004 { 2005 duration: 1, 2006 ease: 'power2', 2007 scale: 1, 2008 filter: 'brightness(100%)', 2009 x: this.mousePos.x - img.rect.width / 2 + dx * 70, 2010 y: this.mousePos.y - img.rect.height / 2 + dy * 70, 2011 rotation: this.lastAngle 2012 }, 2013 0 2014 ) 2015 .to( 2016 img.DOM.el, 2017 { 2018 duration: 0.4, 2019 ease: 'expo', 2020 opacity: 0 2021 }, 2022 0.5 2023 ) 2024 .to( 2025 img.DOM.el, 2026 { 2027 duration: 1.5, 2028 ease: 'power4', 2029 x: \`+=\${dx * 120}\`, 2030 y: \`+=\${dy * 120}\` 2031 }, 2032 0.05 2033 ); 2034 } 2035 2036 destroy() { 2037 this.destroyed = true; 2038 if (this.rafId !== null) { 2039 cancelAnimationFrame(this.rafId); 2040 this.rafId = null; 2041 } 2042 this.container.removeEventListener('mousemove', this.handlePointerMove); 2043 this.container.removeEventListener('touchmove', this.handlePointerMove); 2044 this.container.removeEventListener('mousemove', this.initRender); 2045 this.container.removeEventListener('touchmove', this.initRender);
2046 this.images.forEach(img => { 2047 gsap.killTweensOf(img.DOM.el); 2048 img.destroy(); 2049 }); 2050 } 2051 2052 onImageActivated() { 2053 this.activeImagesCount++; 2054 this.isIdle = false; 2055 } 2056 onImageDeactivated() { 2057 this.activeImagesCount--; 2058 if (this.activeImagesCount === 0) this.isIdle = true; 2059 } 2060} 2061 2062class ImageTrailVariant6 { 2063 constructor(container) { 2064 this.container = container; 2065 this.rafId = null; 2066 this.destroyed = false; 2067 this.DOM = { el: container }; 2068 this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img)); 2069 this.imagesTotal = this.images.length; 2070 this.imgPosition = 0; 2071 this.zIndexVal = 1; 2072 this.activeImagesCount = 0; 2073 this.isIdle = true; 2074 this.threshold = 80; 2075 2076 this.mousePos = { x: 0, y: 0 }; 2077 this.lastMousePos = { x: 0, y: 0 }; 2078 this.cacheMousePos = { x: 0, y: 0 }; 2079 2080 const handlePointerMove = ev => { 2081 const rect = container.getBoundingClientRect(); 2082 this.mousePos = getLocalPointerPos(ev, rect); 2083 }; 2084 container.addEventListener('mousemove', handlePointerMove); 2085 container.addEventListener('touchmove', handlePointerMove); 2086 2087 const initRender = ev => { 2088 const rect = container.getBoundingClientRect(); 2089 this.mousePos = getLocalPointerPos(ev, rect); 2090 this.cacheMousePos = { ...this.mousePos }; 2091 this.rafId = requestAnimationFrame(() => this.render()); 2092 container.removeEventListener('mousemove', initRender); 2093 container.removeEventListener('touchmove', initRender); 2094 }; 2095 container.addEventListener('mousemove', initRender); 2096 container.addEventListener('touchmove', initRender); 2097 this.handlePointerMove = handlePointerMove; 2098 this.initRender = initRender; 2099 } 2100 2101 render() { 2102 if (this.destroyed) return; 2103 2104 let distance = getMouseDistance(this.mousePos, this.lastMousePos); 2105 this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.3); 2106 this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.3); 2107 2108 if (distance > this.threshold) { 2109 this.showNextImage(); 2110 this.lastMousePos = { ...this.mousePos }; 2111 } 2112 if (this.isIdle && this.zIndexVal !== 1) { 2113 this.zIndexVal = 1; 2114 } 2115 this.rafId = requestAnimationFrame(() => this.render()); 2116 } 2117 2118 mapSpeedToSize(speed, minSize, maxSize) { 2119 const maxSpeed = 200; 2120 return minSize + (maxSize - minSize) * Math.min(speed / maxSpeed, 1); 2121 } 2122 mapSpeedToBrightness(speed, minB, maxB) { 2123 const maxSpeed = 70; 2124 return minB + (maxB - minB) * Math.min(speed / maxSpeed, 1); 2125 } 2126 mapSpeedToBlur(speed, minBlur, maxBlur) { 2127 const maxSpeed = 90; 2128 return minBlur + (maxBlur - minBlur) * Math.min(speed / maxSpeed, 1); 2129 } 2130 mapSpeedToGrayscale(speed, minG, maxG) { 2131 const maxSpeed = 90; 2132 return minG + (maxG - minG) * Math.min(speed / maxSpeed, 1); 2133 } 2134 2135 showNextImage() { 2136 let dx = this.mousePos.x - this.cacheMousePos.x; 2137 let dy = this.mousePos.y - this.cacheMousePos.y; 2138 let speed = Math.sqrt(dx * dx + dy * dy); 2139 2140 ++this.zIndexVal; 2141 this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0; 2142 const img = this.images[this.imgPosition]; 2143 2144 let scaleFactor = this.mapSpeedToSize(speed, 0.3, 2); 2145 let brightnessValue = this.mapSpeedToBrightness(speed, 0, 1.3); 2146 let blurValue = this.mapSpeedToBlur(speed, 20, 0); 2147 let grayscaleValue = this.mapSpeedToGrayscale(speed, 600, 0); 2148 2149 gsap.killTweensOf(img.DOM.el); 2150 gsap 2151 .timeline({ 2152 onStart: () => this.onImageActivated(), 2153 onComplete: () => this.onImageDeactivated() 2154 }) 2155 .fromTo( 2156 img.DOM.el, 2157 { 2158 opacity: 1, 2159 scale: 0, 2160 zIndex: this.zIndexVal, 2161 x: this.cacheMousePos.x - img.rect.width / 2, 2162 y: this.cacheMousePos.y - img.rect.height / 2 2163 }, 2164 { 2165 duration: 0.8, 2166 ease: 'power3', 2167 scale: scaleFactor, 2168 filter: \`grayscale(\${grayscaleValue * 100}%) brightness(\${brightnessValue * 100}%) blur(\${blurValue}px)\`, 2169 x: this.mousePos.x - img.rect.width / 2, 2170 y: this.mousePos.y - img.rect.height / 2 2171 }, 2172 0 2173 ) 2174 .fromTo( 2175 img.DOM.inner, 2176 { 2177 scale: 2 2178 }, 2179 { 2180 duration: 0.8, 2181 ease: 'power3', 2182 scale: 1 2183 }, 2184 0 2185 ) 2186 .to( 2187 img.DOM.el, 2188 { 2189 duration: 0.4, 2190 ease: 'power3.in', 2191 opacity: 0, 2192 scale: 0.2 2193 }, 2194 0.45 2195 ); 2196 } 2197 2198 destroy() { 2199 this.destroyed = true; 2200 if (this.rafId !== null) { 2201 cancelAnimationFrame(this.rafId); 2202 this.rafId = null; 2203 } 2204 this.container.removeEventListener('mousemove', this.handlePointerMove); 2205 this.container.removeEventListener('touchmove', this.handlePointerMove); 2206 this.container.removeEventListener('mousemove', this.initRender); 2207 this.container.removeEventListener('touchmove', this.initRender);
2208 this.images.forEach(img => { 2209 gsap.killTweensOf(img.DOM.el); 2210 img.destroy(); 2211 }); 2212 } 2213 2214 onImageActivated() { 2215 this.activeImagesCount++; 2216 this.isIdle = false; 2217 } 2218 onImageDeactivated() { 2219 this.activeImagesCount--; 2220 if (this.activeImagesCount === 0) { 2221 this.isIdle = true; 2222 } 2223 } 2224} 2225 2226function getNewPosition(position, offset, arr) { 2227 const realOffset = Math.abs(offset) % arr.length; 2228 if (position - realOffset >= 0) { 2229 return position - realOffset; 2230 } else { 2231 return arr.length - (realOffset - position); 2232 } 2233} 2234class ImageTrailVariant7 { 2235 constructor(container) { 2236 this.container = container; 2237 this.rafId = null; 2238 this.destroyed = false; 2239 this.DOM = { el: container }; 2240 this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img)); 2241 this.imagesTotal = this.images.length; 2242 this.imgPosition = 0; 2243 this.zIndexVal = 1; 2244 this.activeImagesCount = 0; 2245 this.isIdle = true; 2246 this.threshold = 80; 2247 2248 this.mousePos = { x: 0, y: 0 }; 2249 this.lastMousePos = { x: 0, y: 0 }; 2250 this.cacheMousePos = { x: 0, y: 0 }; 2251 2252 this.visibleImagesCount = 0; 2253 this.visibleImagesTotal = 9; 2254 this.visibleImagesTotal = Math.min(this.visibleImagesTotal, this.imagesTotal - 1); 2255 2256 const handlePointerMove = ev => { 2257 const rect = container.getBoundingClientRect(); 2258 this.mousePos = getLocalPointerPos(ev, rect); 2259 }; 2260 container.addEventListener('mousemove', handlePointerMove); 2261 container.addEventListener('touchmove', handlePointerMove); 2262 2263 const initRender = ev => { 2264 const rect = container.getBoundingClientRect(); 2265 this.mousePos = getLocalPointerPos(ev, rect); 2266 this.cacheMousePos = { ...this.mousePos }; 2267 this.rafId = requestAnimationFrame(() => this.render()); 2268 container.removeEventListener('mousemove', initRender); 2269 container.removeEventListener('touchmove', initRender); 2270 }; 2271 container.addEventListener('mousemove', initRender); 2272 container.addEventListener('touchmove', initRender); 2273 this.handlePointerMove = handlePointerMove; 2274 this.initRender = initRender; 2275 } 2276 2277 render() { 2278 if (this.destroyed) return; 2279 2280 let distance = getMouseDistance(this.mousePos, this.lastMousePos); 2281 this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.3); 2282 this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.3); 2283 2284 if (distance > this.threshold) { 2285 this.showNextImage(); 2286 this.lastMousePos = { ...this.mousePos }; 2287 } 2288 if (this.isIdle && this.zIndexVal !== 1) this.zIndexVal = 1; 2289 2290 this.rafId = requestAnimationFrame(() => this.render()); 2291 } 2292 2293 showNextImage() { 2294 ++this.zIndexVal; 2295 this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0; 2296 const img = this.images[this.imgPosition]; 2297 ++this.visibleImagesCount; 2298 2299 gsap.killTweensOf(img.DOM.el); 2300 const scaleValue = gsap.utils.random(0.5, 1.6); 2301 2302 gsap 2303 .timeline({ 2304 onStart: () => this.onImageActivated(), 2305 onComplete: () => this.onImageDeactivated() 2306 }) 2307 .fromTo( 2308 img.DOM.el, 2309 { 2310 scale: scaleValue - Math.max(gsap.utils.random(0.2, 0.6), 0), 2311 rotationZ: 0, 2312 opacity: 1, 2313 zIndex: this.zIndexVal, 2314 x: this.cacheMousePos.x - img.rect.width / 2, 2315 y: this.cacheMousePos.y - img.rect.height / 2 2316 }, 2317 { 2318 duration: 0.4, 2319 ease: 'power3', 2320 scale: scaleValue, 2321 rotationZ: gsap.utils.random(-3, 3), 2322 x: this.mousePos.x - img.rect.width / 2, 2323 y: this.mousePos.y - img.rect.height / 2 2324 }, 2325 0 2326 ); 2327 2328 if (this.visibleImagesCount >= this.visibleImagesTotal) { 2329 const lastInQueue = getNewPosition(this.imgPosition, this.visibleImagesTotal, this.images); 2330 const oldImg = this.images[lastInQueue]; 2331 gsap.to(oldImg.DOM.el, { 2332 duration: 0.4, 2333 ease: 'power4', 2334 opacity: 0, 2335 scale: 1.3, 2336 onComplete: () => { 2337 if (this.activeImagesCount === 0) { 2338 this.isIdle = true; 2339 } 2340 } 2341 }); 2342 } 2343 } 2344 2345 destroy() { 2346 this.destroyed = true; 2347 if (this.rafId !== null) { 2348 cancelAnimationFrame(this.rafId); 2349 this.rafId = null; 2350 } 2351 this.container.removeEventListener('mousemove', this.handlePointerMove); 2352 this.container.removeEventListener('touchmove', this.handlePointerMove); 2353 this.container.removeEventListener('mousemove', this.initRender); 2354 this.container.removeEventListener('touchmove', this.initRender);
2355 this.images.forEach(img => { 2356 gsap.killTweensOf(img.DOM.el); 2357 img.destroy(); 2358 }); 2359 } 2360 2361 onImageActivated() { 2362 this.activeImagesCount++; 2363 this.isIdle = false; 2364 } 2365 onImageDeactivated() { 2366 this.activeImagesCount--; 2367 } 2368} 2369 2370class ImageTrailVariant8 { 2371 constructor(container) { 2372 this.container = container; 2373 this.rafId = null; 2374 this.destroyed = false; 2375 this.DOM = { el: container }; 2376 this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img)); 2377 this.imagesTotal = this.images.length; 2378 this.imgPosition = 0; 2379 this.zIndexVal = 1; 2380 this.activeImagesCount = 0; 2381 this.isIdle = true; 2382 this.threshold = 80; 2383 2384 this.mousePos = { x: 0, y: 0 }; 2385 this.lastMousePos = { x: 0, y: 0 }; 2386 this.cacheMousePos = { x: 0, y: 0 }; 2387 2388 this.rotation = { x: 0, y: 0 }; 2389 this.cachedRotation = { x: 0, y: 0 }; 2390 this.zValue = 0; 2391 this.cachedZValue = 0; 2392 2393 const handlePointerMove = ev => { 2394 const rect = container.getBoundingClientRect(); 2395 this.mousePos = getLocalPointerPos(ev, rect); 2396 }; 2397 container.addEventListener('mousemove', handlePointerMove); 2398 container.addEventListener('touchmove', handlePointerMove); 2399 2400 const initRender = ev => { 2401 const rect = container.getBoundingClientRect(); 2402 this.mousePos = getLocalPointerPos(ev, rect); 2403 this.cacheMousePos = { ...this.mousePos }; 2404 this.rafId = requestAnimationFrame(() => this.render()); 2405 container.removeEventListener('mousemove', initRender); 2406 container.removeEventListener('touchmove', initRender); 2407 }; 2408 container.addEventListener('mousemove', initRender); 2409 container.addEventListener('touchmove', initRender); 2410 this.handlePointerMove = handlePointerMove; 2411 this.initRender = initRender; 2412 } 2413 2414 render() { 2415 if (this.destroyed) return; 2416 2417 let distance = getMouseDistance(this.mousePos, this.lastMousePos); 2418 this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); 2419 this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); 2420 2421 if (distance > this.threshold) { 2422 this.showNextImage(); 2423 this.lastMousePos = { ...this.mousePos }; 2424 } 2425 if (this.isIdle && this.zIndexVal !== 1) { 2426 this.zIndexVal = 1; 2427 } 2428 this.rafId = requestAnimationFrame(() => this.render()); 2429 } 2430 2431 showNextImage() { 2432 const rect = this.container.getBoundingClientRect(); 2433 const centerX = rect.width / 2; 2434 const centerY = rect.height / 2; 2435 const relX = this.mousePos.x - centerX; 2436 const relY = this.mousePos.y - centerY; 2437 2438 this.rotation.x = -(relY / centerY) * 30; 2439 this.rotation.y = (relX / centerX) * 30; 2440 this.cachedRotation = { ...this.rotation }; 2441 2442 const distanceFromCenter = Math.sqrt(relX * relX + relY * relY); 2443 const maxDistance = Math.sqrt(centerX * centerX + centerY * centerY); 2444 const proportion = distanceFromCenter / maxDistance; 2445 this.zValue = proportion * 1200 - 600; 2446 this.cachedZValue = this.zValue; 2447 const normalizedZ = (this.zValue + 600) / 1200; 2448 const brightness = 0.2 + normalizedZ * 2.3; 2449 2450 ++this.zIndexVal; 2451 this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0; 2452 const img = this.images[this.imgPosition]; 2453 gsap.killTweensOf(img.DOM.el); 2454 2455 gsap 2456 .timeline({ 2457 onStart: () => this.onImageActivated(), 2458 onComplete: () => this.onImageDeactivated() 2459 }) 2460 .set(this.DOM.el, { perspective: 1000 }, 0) 2461 .fromTo( 2462 img.DOM.el, 2463 { 2464 opacity: 1, 2465 z: 0, 2466 scale: 1 + this.cachedZValue / 1000, 2467 zIndex: this.zIndexVal, 2468 x: this.cacheMousePos.x - img.rect.width / 2, 2469 y: this.cacheMousePos.y - img.rect.height / 2, 2470 rotationX: this.cachedRotation.x, 2471 rotationY: this.cachedRotation.y, 2472 filter: \`brightness(\${brightness})\` 2473 }, 2474 { 2475 duration: 1, 2476 ease: 'expo', 2477 scale: 1 + this.zValue / 1000, 2478 x: this.mousePos.x - img.rect.width / 2, 2479 y: this.mousePos.y - img.rect.height / 2, 2480 rotationX: this.rotation.x, 2481 rotationY: this.rotation.y 2482 }, 2483 0 2484 ) 2485 .to( 2486 img.DOM.el, 2487 { 2488 duration: 0.4, 2489 ease: 'power2', 2490 opacity: 0,
2491 z: -800 2492 }, 2493 0.3 2494 ); 2495 } 2496 2497 destroy() { 2498 this.destroyed = true; 2499 if (this.rafId !== null) { 2500 cancelAnimationFrame(this.rafId); 2501 this.rafId = null; 2502 } 2503 this.container.removeEventListener('mousemove', this.handlePointerMove); 2504 this.container.removeEventListener('touchmove', this.handlePointerMove); 2505 this.container.removeEventListener('mousemove', this.initRender); 2506 this.container.removeEventListener('touchmove', this.initRender); 2507 this.images.forEach(img => { 2508 gsap.killTweensOf(img.DOM.el); 2509 img.destroy(); 2510 }); 2511 } 2512 2513 onImageActivated() { 2514 this.activeImagesCount++; 2515 this.isIdle = false; 2516 } 2517 onImageDeactivated() { 2518 this.activeImagesCount--; 2519 if (this.activeImagesCount === 0) this.isIdle = true; 2520 } 2521} 2522 2523const variantMap = { 2524 1: ImageTrailVariant1, 2525 2: ImageTrailVariant2, 2526 3: ImageTrailVariant3, 2527 4: ImageTrailVariant4, 2528 5: ImageTrailVariant5, 2529 6: ImageTrailVariant6, 2530 7: ImageTrailVariant7, 2531 8: ImageTrailVariant8 2532}; 2533 2534export default function ImageTrail({ items = [], variant = 1 }) { 2535 const containerRef = useRef(null); 2536 2537 useEffect(() => { 2538 if (!containerRef.current) return; 2539 2540 const Cls = variantMap[variant] || variantMap[1]; 2541 const instance = new Cls(containerRef.current); 2542 2543 return () => { 2544 instance.destroy(); 2545 }; 2546 }, [variant, items]); 2547 2548 return ( 2549 <div className="w-full h-full relative z-[100] rounded-lg bg-transparent overflow-visible" ref={containerRef}> 2550 {items.map((url, i) => ( 2551 <div 2552 className="content__img w-[190px] aspect-[1.1] rounded-[15px] absolute top-0 left-0 opacity-0 overflow-hidden [will-change:transform,filter]" 2553 key={i} 2554 > 2555 <div 2556 className="content__img-inner bg-center bg-cover w-[calc(100%+20px)] h-[calc(100%+20px)] absolute top-[-10px] left-[-10px]" 2557 style={{ backgroundImage: \`url(\${url})\` }} 2558 /> 2559 </div> 2560 ))} 2561 </div> 2562 ); 2563} 2564`,O=`'use client'; 2565 2566import { gsap } from 'gsap'; 2567import { type JSX, useEffect, useRef } from 'react'; 2568import './ImageTrail.css'; 2569 2570function lerp(a: number, b: number, n: number): number { 2571 return (1 - n) * a + n * b; 2572} 2573 2574function getLocalPointerPos(e: MouseEvent | TouchEvent, rect: DOMRect): { x: number; y: number } { 2575 let clientX = 0, 2576 clientY = 0; 2577 if ('touches' in e && e.touches.length > 0) { 2578 clientX = e.touches[0].clientX; 2579 clientY = e.touches[0].clientY; 2580 } else if ('clientX' in e) { 2581 clientX = e.clientX; 2582 clientY = e.clientY; 2583 } 2584 return { 2585 x: clientX - rect.left, 2586 y: clientY - rect.top 2587 }; 2588} 2589 2590function getMouseDistance(p1: { x: number; y: number }, p2: { x: number; y: number }): number { 2591 const dx = p1.x - p2.x; 2592 const dy = p1.y - p2.y; 2593 return Math.hypot(dx, dy); 2594} 2595 2596class ImageItem { 2597 public DOM: { el: HTMLDivElement; inner: HTMLDivElement | null } = { 2598 el: null as unknown as HTMLDivElement, 2599 inner: null 2600 }; 2601 public defaultStyle: gsap.TweenVars = { scale: 1, x: 0, y: 0, opacity: 0 }; 2602 public rect: DOMRect | null = null; 2603 private resize!: () => void; 2604 2605 constructor(DOM_el: HTMLDivElement) { 2606 this.DOM.el = DOM_el; 2607 this.DOM.inner = this.DOM.el.querySelector('.content__img-inner'); 2608 this.getRect(); 2609 this.initEvents(); 2610 } 2611 2612 private initEvents() { 2613 this.resize = () => { 2614 gsap.set(this.DOM.el, this.defaultStyle); 2615 this.getRect(); 2616 }; 2617 window.addEventListener('resize', this.resize); 2618 } 2619 2620 private getRect() { 2621 this.rect = this.DOM.el.getBoundingClientRect(); 2622 } 2623 2624 public destroy() { 2625 window.removeEventListener('resize', this.resize); 2626 } 2627} 2628 2629class ImageTrailVariant1 { 2630 private container: HTMLDivElement; 2631 private DOM: { el: HTMLDivElement }; 2632 private images: ImageItem[]; 2633 private imagesTotal: number; 2634 private imgPosition: number; 2635 private zIndexVal: number; 2636 private activeImagesCount: number; 2637 private isIdle: boolean; 2638 private threshold: number; 2639 private mousePos: { x: number; y: number }; 2640 private lastMousePos: { x: number; y: number }; 2641 private cacheMousePos: { x: number; y: number }; 2642 private rafId: number | null = null; 2643 private destroyed = false;
2644 private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void; 2645 private initRender!: (ev: MouseEvent | TouchEvent) => void; 2646 2647 constructor(container: HTMLDivElement) { 2648 this.container = container; 2649 this.DOM = { el: container }; 2650 this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img as HTMLDivElement)); 2651 this.imagesTotal = this.images.length; 2652 this.imgPosition = 0; 2653 this.zIndexVal = 1; 2654 this.activeImagesCount = 0; 2655 this.isIdle = true; 2656 this.threshold = 80; 2657 this.mousePos = { x: 0, y: 0 }; 2658 this.lastMousePos = { x: 0, y: 0 }; 2659 this.cacheMousePos = { x: 0, y: 0 }; 2660 2661 const handlePointerMove = (ev: MouseEvent | TouchEvent) => { 2662 const rect = this.container.getBoundingClientRect(); 2663 this.mousePos = getLocalPointerPos(ev, rect); 2664 }; 2665 container.addEventListener('mousemove', handlePointerMove); 2666 container.addEventListener('touchmove', handlePointerMove); 2667 2668 const initRender = (ev: MouseEvent | TouchEvent) => { 2669 const rect = this.container.getBoundingClientRect(); 2670 this.mousePos = getLocalPointerPos(ev, rect); 2671 this.cacheMousePos = { ...this.mousePos }; 2672 this.rafId = requestAnimationFrame(() => this.render()); 2673 container.removeEventListener('mousemove', initRender as EventListener); 2674 container.removeEventListener('touchmove', initRender as EventListener); 2675 }; 2676 container.addEventListener('mousemove', initRender as EventListener); 2677 container.addEventListener('touchmove', initRender as EventListener); 2678 this.handlePointerMove = handlePointerMove; 2679 this.initRender = initRender; 2680 } 2681 2682 private render() { 2683 if (this.destroyed) return; 2684 2685 const distance = getMouseDistance(this.mousePos, this.lastMousePos); 2686 this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); 2687 this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); 2688 2689 if (distance > this.threshold) { 2690 this.showNextImage(); 2691 this.lastMousePos = { ...this.mousePos }; 2692 } 2693 if (this.isIdle && this.zIndexVal !== 1) { 2694 this.zIndexVal = 1; 2695 } 2696 this.rafId = requestAnimationFrame(() => this.render()); 2697 } 2698 2699 private showNextImage() { 2700 ++this.zIndexVal; 2701 this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0; 2702 const img = this.images[this.imgPosition]; 2703 2704 gsap.killTweensOf(img.DOM.el); 2705 gsap 2706 .timeline({ 2707 onStart: () => this.onImageActivated(), 2708 onComplete: () => this.onImageDeactivated() 2709 }) 2710 .fromTo( 2711 img.DOM.el, 2712 { 2713 opacity: 1, 2714 scale: 1, 2715 zIndex: this.zIndexVal, 2716 x: this.cacheMousePos.x - (img.rect?.width ?? 0) / 2, 2717 y: this.cacheMousePos.y - (img.rect?.height ?? 0) / 2 2718 }, 2719 { 2720 duration: 0.4, 2721 ease: 'power1', 2722 x: this.mousePos.x - (img.rect?.width ?? 0) / 2, 2723 y: this.mousePos.y - (img.rect?.height ?? 0) / 2 2724 }, 2725 0 2726 ) 2727 .to( 2728 img.DOM.el, 2729 { 2730 duration: 0.4, 2731 ease: 'power3', 2732 opacity: 0, 2733 scale: 0.2 2734 }, 2735 0.4 2736 ); 2737 } 2738 2739 public destroy() { 2740 this.destroyed = true; 2741 if (this.rafId !== null) { 2742 cancelAnimationFrame(this.rafId); 2743 this.rafId = null; 2744 } 2745 this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener); 2746 this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener); 2747 this.container.removeEventListener('mousemove', this.initRender as EventListener); 2748 this.container.removeEventListener('touchmove', this.initRender as EventListener); 2749 this.images.forEach(img => { 2750 gsap.killTweensOf(img.DOM.el); 2751 img.destroy(); 2752 }); 2753 } 2754 2755 private onImageActivated() { 2756 this.activeImagesCount++; 2757 this.isIdle = false; 2758 } 2759 2760 private onImageDeactivated() { 2761 this.activeImagesCount--; 2762 if (this.activeImagesCount === 0) { 2763 this.isIdle = true; 2764 } 2765 } 2766} 2767 2768class ImageTrailVariant2 { 2769 private container: HTMLDivElement; 2770 private DOM: { el: HTMLDivElement }; 2771 private images: ImageItem[]; 2772 private imagesTotal: number; 2773 private imgPosition: number; 2774 private zIndexVal: number; 2775 private activeImagesCount: number; 2776 private isIdle: boolean; 2777 private threshold: number; 2778 private mousePos: { x: number; y: number }; 2779 private lastMousePos: { x: number; y: number }; 2780 private cacheMousePos: { x: number; y: number }; 2781 private rafId: number | null = null; 2782 private destroyed = false;
2783 private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void; 2784 private initRender!: (ev: MouseEvent | TouchEvent) => void; 2785 2786 constructor(container: HTMLDivElement) { 2787 this.container = container; 2788 this.DOM = { el: container }; 2789 this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img as HTMLDivElement)); 2790 this.imagesTotal = this.images.length; 2791 this.imgPosition = 0; 2792 this.zIndexVal = 1; 2793 this.activeImagesCount = 0; 2794 this.isIdle = true; 2795 this.threshold = 80; 2796 this.mousePos = { x: 0, y: 0 }; 2797 this.lastMousePos = { x: 0, y: 0 }; 2798 this.cacheMousePos = { x: 0, y: 0 }; 2799 2800 const handlePointerMove = (ev: MouseEvent | TouchEvent) => { 2801 const rect = container.getBoundingClientRect(); 2802 this.mousePos = getLocalPointerPos(ev, rect); 2803 }; 2804 container.addEventListener('mousemove', handlePointerMove); 2805 container.addEventListener('touchmove', handlePointerMove); 2806 2807 const initRender = (ev: MouseEvent | TouchEvent) => { 2808 const rect = container.getBoundingClientRect(); 2809 this.mousePos = getLocalPointerPos(ev, rect); 2810 this.cacheMousePos = { ...this.mousePos }; 2811 this.rafId = requestAnimationFrame(() => this.render()); 2812 container.removeEventListener('mousemove', initRender as EventListener); 2813 container.removeEventListener('touchmove', initRender as EventListener); 2814 }; 2815 container.addEventListener('mousemove', initRender as EventListener); 2816 container.addEventListener('touchmove', initRender as EventListener); 2817 this.handlePointerMove = handlePointerMove; 2818 this.initRender = initRender; 2819 } 2820 2821 private render() { 2822 if (this.destroyed) return; 2823 2824 const distance = getMouseDistance(this.mousePos, this.lastMousePos); 2825 this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); 2826 this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); 2827 2828 if (distance > this.threshold) { 2829 this.showNextImage(); 2830 this.lastMousePos = { ...this.mousePos }; 2831 } 2832 if (this.isIdle && this.zIndexVal !== 1) { 2833 this.zIndexVal = 1; 2834 } 2835 this.rafId = requestAnimationFrame(() => this.render()); 2836 } 2837 2838 private showNextImage() { 2839 ++this.zIndexVal; 2840 this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0; 2841 const img = this.images[this.imgPosition]; 2842 2843 gsap.killTweensOf(img.DOM.el); 2844 gsap 2845 .timeline({ 2846 onStart: () => this.onImageActivated(), 2847 onComplete: () => this.onImageDeactivated() 2848 }) 2849 .fromTo( 2850 img.DOM.el, 2851 { 2852 opacity: 1, 2853 scale: 0, 2854 zIndex: this.zIndexVal, 2855 x: this.cacheMousePos.x - (img.rect?.width ?? 0) / 2, 2856 y: this.cacheMousePos.y - (img.rect?.height ?? 0) / 2 2857 }, 2858 { 2859 duration: 0.4, 2860 ease: 'power1', 2861 scale: 1, 2862 x: this.mousePos.x - (img.rect?.width ?? 0) / 2, 2863 y: this.mousePos.y - (img.rect?.height ?? 0) / 2 2864 }, 2865 0 2866 ) 2867 .fromTo( 2868 img.DOM.inner, 2869 { scale: 2.8, filter: 'brightness(250%)' }, 2870 { 2871 duration: 0.4, 2872 ease: 'power1', 2873 scale: 1, 2874 filter: 'brightness(100%)' 2875 }, 2876 0 2877 ) 2878 .to( 2879 img.DOM.el, 2880 { 2881 duration: 0.4, 2882 ease: 'power2', 2883 opacity: 0, 2884 scale: 0.2 2885 }, 2886 0.45 2887 ); 2888 } 2889 2890 public destroy() { 2891 this.destroyed = true; 2892 if (this.rafId !== null) { 2893 cancelAnimationFrame(this.rafId); 2894 this.rafId = null; 2895 } 2896 this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener); 2897 this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener); 2898 this.container.removeEventListener('mousemove', this.initRender as EventListener); 2899 this.container.removeEventListener('touchmove', this.initRender as EventListener); 2900 this.images.forEach(img => { 2901 gsap.killTweensOf(img.DOM.el); 2902 img.destroy(); 2903 }); 2904 } 2905 2906 private onImageActivated() { 2907 this.activeImagesCount++; 2908 this.isIdle = false;
2909 } 2910 2911 private onImageDeactivated() { 2912 this.activeImagesCount--; 2913 if (this.activeImagesCount === 0) { 2914 this.isIdle = true; 2915 } 2916 } 2917} 2918 2919class ImageTrailVariant3 { 2920 private container: HTMLDivElement; 2921 private DOM: { el: HTMLDivElement }; 2922 private images: ImageItem[]; 2923 private imagesTotal: number; 2924 private imgPosition: number; 2925 private zIndexVal: number; 2926 private activeImagesCount: number; 2927 private isIdle: boolean; 2928 private threshold: number; 2929 private mousePos: { x: number; y: number }; 2930 private lastMousePos: { x: number; y: number }; 2931 private cacheMousePos: { x: number; y: number }; 2932 private rafId: number | null = null; 2933 private destroyed = false; 2934 private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void; 2935 private initRender!: (ev: MouseEvent | TouchEvent) => void; 2936 2937 constructor(container: HTMLDivElement) { 2938 this.container = container; 2939 this.DOM = { el: container }; 2940 this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img as HTMLDivElement)); 2941 this.imagesTotal = this.images.length; 2942 this.imgPosition = 0; 2943 this.zIndexVal = 1; 2944 this.activeImagesCount = 0; 2945 this.isIdle = true; 2946 this.threshold = 80; 2947 this.mousePos = { x: 0, y: 0 }; 2948 this.lastMousePos = { x: 0, y: 0 }; 2949 this.cacheMousePos = { x: 0, y: 0 }; 2950 2951 const handlePointerMove = (ev: MouseEvent | TouchEvent) => { 2952 const rect = container.getBoundingClientRect(); 2953 this.mousePos = getLocalPointerPos(ev, rect); 2954 }; 2955 container.addEventListener('mousemove', handlePointerMove); 2956 container.addEventListener('touchmove', handlePointerMove); 2957 2958 const initRender = (ev: MouseEvent | TouchEvent) => { 2959 const rect = container.getBoundingClientRect(); 2960 this.mousePos = getLocalPointerPos(ev, rect); 2961 this.cacheMousePos = { ...this.mousePos }; 2962 this.rafId = requestAnimationFrame(() => this.render()); 2963 container.removeEventListener('mousemove', initRender as EventListener); 2964 container.removeEventListener('touchmove', initRender as EventListener); 2965 }; 2966 container.addEventListener('mousemove', initRender as EventListener); 2967 container.addEventListener('touchmove', initRender as EventListener); 2968 this.handlePointerMove = handlePointerMove; 2969 this.initRender = initRender; 2970 } 2971 2972 private render() { 2973 if (this.destroyed) return; 2974 2975 const distance = getMouseDistance(this.mousePos, this.lastMousePos); 2976 this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); 2977 this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); 2978 2979 if (distance > this.threshold) { 2980 this.showNextImage(); 2981 this.lastMousePos = { ...this.mousePos }; 2982 } 2983 if (this.isIdle && this.zIndexVal !== 1) { 2984 this.zIndexVal = 1; 2985 } 2986 this.rafId = requestAnimationFrame(() => this.render()); 2987 } 2988 2989 private showNextImage() { 2990 ++this.zIndexVal; 2991 this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0; 2992 const img = this.images[this.imgPosition]; 2993 2994 gsap.killTweensOf(img.DOM.el); 2995 gsap 2996 .timeline({ 2997 onStart: () => this.onImageActivated(), 2998 onComplete: () => this.onImageDeactivated() 2999 }) 3000 .fromTo( 3001 img.DOM.el, 3002 { 3003 opacity: 1, 3004 scale: 0, 3005 zIndex: this.zIndexVal, 3006 xPercent: 0, 3007 yPercent: 0, 3008 x: this.cacheMousePos.x - (img.rect?.width ?? 0) / 2, 3009 y: this.cacheMousePos.y - (img.rect?.height ?? 0) / 2 3010 }, 3011 { 3012 duration: 0.4, 3013 ease: 'power1', 3014 scale: 1, 3015 x: this.mousePos.x - (img.rect?.width ?? 0) / 2, 3016 y: this.mousePos.y - (img.rect?.height ?? 0) / 2 3017 }, 3018 0 3019 ) 3020 .fromTo( 3021 img.DOM.inner, 3022 { scale: 1.2 }, 3023 { 3024 duration: 0.4, 3025 ease: 'power1', 3026 scale: 1 3027 }, 3028 0 3029 ) 3030 .to( 3031 img.DOM.el, 3032 { 3033 duration: 0.6, 3034 ease: 'power2', 3035 opacity: 0, 3036 scale: 0.2, 3037 xPercent: () => gsap.utils.random(-30, 30), 3038 yPercent: -200 3039 }, 3040 0.6 3041 ); 3042 } 3043
3044 public destroy() { 3045 this.destroyed = true; 3046 if (this.rafId !== null) { 3047 cancelAnimationFrame(this.rafId); 3048 this.rafId = null; 3049 } 3050 this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener); 3051 this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener); 3052 this.container.removeEventListener('mousemove', this.initRender as EventListener); 3053 this.container.removeEventListener('touchmove', this.initRender as EventListener); 3054 this.images.forEach(img => { 3055 gsap.killTweensOf(img.DOM.el); 3056 img.destroy(); 3057 }); 3058 } 3059 3060 private onImageActivated() { 3061 this.activeImagesCount++; 3062 this.isIdle = false; 3063 } 3064 3065 private onImageDeactivated() { 3066 this.activeImagesCount--; 3067 if (this.activeImagesCount === 0) { 3068 this.isIdle = true; 3069 } 3070 } 3071} 3072 3073class ImageTrailVariant4 { 3074 private container: HTMLDivElement; 3075 private DOM: { el: HTMLDivElement }; 3076 private images: ImageItem[]; 3077 private imagesTotal: number; 3078 private imgPosition: number; 3079 private zIndexVal: number; 3080 private activeImagesCount: number; 3081 private isIdle: boolean; 3082 private threshold: number; 3083 private mousePos: { x: number; y: number }; 3084 private lastMousePos: { x: number; y: number }; 3085 private cacheMousePos: { x: number; y: number }; 3086 private rafId: number | null = null; 3087 private destroyed = false; 3088 private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void; 3089 private initRender!: (ev: MouseEvent | TouchEvent) => void; 3090 3091 constructor(container: HTMLDivElement) { 3092 this.container = container; 3093 this.DOM = { el: container }; 3094 this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img as HTMLDivElement)); 3095 this.imagesTotal = this.images.length; 3096 this.imgPosition = 0; 3097 this.zIndexVal = 1; 3098 this.activeImagesCount = 0; 3099 this.isIdle = true; 3100 this.threshold = 80; 3101 this.mousePos = { x: 0, y: 0 }; 3102 this.lastMousePos = { x: 0, y: 0 }; 3103 this.cacheMousePos = { x: 0, y: 0 }; 3104 3105 const handlePointerMove = (ev: MouseEvent | TouchEvent) => { 3106 const rect = container.getBoundingClientRect(); 3107 this.mousePos = getLocalPointerPos(ev, rect); 3108 }; 3109 container.addEventListener('mousemove', handlePointerMove); 3110 container.addEventListener('touchmove', handlePointerMove); 3111 3112 const initRender = (ev: MouseEvent | TouchEvent) => { 3113 const rect = container.getBoundingClientRect(); 3114 this.mousePos = getLocalPointerPos(ev, rect); 3115 this.cacheMousePos = { ...this.mousePos }; 3116 this.rafId = requestAnimationFrame(() => this.render()); 3117 container.removeEventListener('mousemove', initRender as EventListener); 3118 container.removeEventListener('touchmove', initRender as EventListener); 3119 }; 3120 container.addEventListener('mousemove', initRender as EventListener); 3121 container.addEventListener('touchmove', initRender as EventListener); 3122 this.handlePointerMove = handlePointerMove; 3123 this.initRender = initRender; 3124 } 3125 3126 private render() { 3127 if (this.destroyed) return; 3128 3129 const distance = getMouseDistance(this.mousePos, this.lastMousePos); 3130 if (distance > this.threshold) { 3131 this.showNextImage(); 3132 this.lastMousePos = { ...this.mousePos }; 3133 } 3134 this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); 3135 this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); 3136 3137 if (this.isIdle && this.zIndexVal !== 1) this.zIndexVal = 1; 3138 this.rafId = requestAnimationFrame(() => this.render()); 3139 } 3140 3141 private showNextImage() { 3142 ++this.zIndexVal; 3143 this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0; 3144 const img = this.images[this.imgPosition]; 3145 gsap.killTweensOf(img.DOM.el); 3146 3147 let dx = this.mousePos.x - this.cacheMousePos.x; 3148 let dy = this.mousePos.y - this.cacheMousePos.y; 3149 let distance = Math.sqrt(dx * dx + dy * dy); 3150 if (distance !== 0) { 3151 dx /= distance; 3152 dy /= distance; 3153 } 3154 dx *= distance / 100; 3155 dy *= distance / 100; 3156 3157 gsap 3158 .timeline({ 3159 onStart: () => this.onImageActivated(), 3160 onComplete: () => this.onImageDeactivated() 3161 }) 3162 .fromTo( 3163 img.DOM.el, 3164 { 3165 opacity: 1, 3166 scale: 0, 3167 zIndex: this.zIndexVal, 3168 x: this.cacheMousePos.x - (img.rect?.width ?? 0) / 2, 3169 y: this.cacheMousePos.y - (img.rect?.height ?? 0) / 2 3170 }, 3171 { 3172 duration: 0.4, 3173 ease: 'power1', 3174 scale: 1, 3175 x: this.mousePos.x - (img.rect?.width ?? 0) / 2, 3176 y: this.mousePos.y - (img.rect?.height ?? 0) / 2 3177 }, 3178 0 3179 ) 3180 .fromTo( 3181 img.DOM.inner, 3182 { 3183 scale: 2, 3184 filter: \`brightness(\${Math.max((400 * distance) / 100, 100)}%) contrast(\${Math.max( 3185 (400 * distance) / 100, 3186 100 3187 )}%)\` 3188 }, 3189 { 3190 duration: 0.4, 3191 ease: 'power1', 3192 scale: 1, 3193 filter: 'brightness(100%) contrast(100%)' 3194 }, 3195 0 3196 ) 3197 .to( 3198 img.DOM.el, 3199 { 3200 duration: 0.4, 3201 ease: 'power3', 3202 opacity: 0 3203 }, 3204 0.4 3205 ) 3206 .to( 3207 img.DOM.el, 3208 { 3209 duration: 1.5, 3210 ease: 'power4', 3211 x: \`+=\${dx * 110}\`, 3212 y: \`+=\${dy * 110}\` 3213 }, 3214 0.05 3215 ); 3216 } 3217
3218 public destroy() { 3219 this.destroyed = true; 3220 if (this.rafId !== null) { 3221 cancelAnimationFrame(this.rafId); 3222 this.rafId = null; 3223 } 3224 this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener); 3225 this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener); 3226 this.container.removeEventListener('mousemove', this.initRender as EventListener); 3227 this.container.removeEventListener('touchmove', this.initRender as EventListener); 3228 this.images.forEach(img => { 3229 gsap.killTweensOf(img.DOM.el); 3230 img.destroy(); 3231 }); 3232 } 3233 3234 private onImageActivated() { 3235 this.activeImagesCount++; 3236 this.isIdle = false; 3237 } 3238 3239 private onImageDeactivated() { 3240 this.activeImagesCount--; 3241 if (this.activeImagesCount === 0) { 3242 this.isIdle = true; 3243 } 3244 } 3245} 3246 3247class ImageTrailVariant5 { 3248 private container: HTMLDivElement; 3249 private DOM: { el: HTMLDivElement }; 3250 private images: ImageItem[]; 3251 private imagesTotal: number; 3252 private imgPosition: number; 3253 private zIndexVal: number; 3254 private activeImagesCount: number; 3255 private isIdle: boolean; 3256 private threshold: number; 3257 private mousePos: { x: number; y: number }; 3258 private lastMousePos: { x: number; y: number }; 3259 private cacheMousePos: { x: number; y: number }; 3260 private rafId: number | null = null; 3261 private destroyed = false; 3262 private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void; 3263 private initRender!: (ev: MouseEvent | TouchEvent) => void; 3264 private lastAngle: number; 3265 3266 constructor(container: HTMLDivElement) { 3267 this.container = container; 3268 this.DOM = { el: container }; 3269 this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img as HTMLDivElement)); 3270 this.imagesTotal = this.images.length; 3271 this.imgPosition = 0; 3272 this.zIndexVal = 1; 3273 this.activeImagesCount = 0; 3274 this.isIdle = true; 3275 this.threshold = 80; 3276 this.mousePos = { x: 0, y: 0 }; 3277 this.lastMousePos = { x: 0, y: 0 }; 3278 this.cacheMousePos = { x: 0, y: 0 }; 3279 this.lastAngle = 0; 3280 3281 const handlePointerMove = (ev: MouseEvent | TouchEvent) => { 3282 const rect = container.getBoundingClientRect(); 3283 this.mousePos = getLocalPointerPos(ev, rect); 3284 }; 3285 container.addEventListener('mousemove', handlePointerMove); 3286 container.addEventListener('touchmove', handlePointerMove); 3287 3288 const initRender = (ev: MouseEvent | TouchEvent) => { 3289 const rect = container.getBoundingClientRect(); 3290 this.mousePos = getLocalPointerPos(ev, rect); 3291 this.cacheMousePos = { ...this.mousePos }; 3292 this.rafId = requestAnimationFrame(() => this.render()); 3293 container.removeEventListener('mousemove', initRender as EventListener); 3294 container.removeEventListener('touchmove', initRender as EventListener); 3295 }; 3296 container.addEventListener('mousemove', initRender as EventListener); 3297 container.addEventListener('touchmove', initRender as EventListener); 3298 this.handlePointerMove = handlePointerMove; 3299 this.initRender = initRender; 3300 } 3301 3302 private render() { 3303 if (this.destroyed) return; 3304 3305 const distance = getMouseDistance(this.mousePos, this.lastMousePos); 3306 if (distance > this.threshold) { 3307 this.showNextImage(); 3308 this.lastMousePos = { ...this.mousePos }; 3309 } 3310 this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); 3311 this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); 3312 if (this.isIdle && this.zIndexVal !== 1) this.zIndexVal = 1; 3313 this.rafId = requestAnimationFrame(() => this.render()); 3314 } 3315 3316 private showNextImage() { 3317 let dx = this.mousePos.x - this.cacheMousePos.x; 3318 let dy = this.mousePos.y - this.cacheMousePos.y; 3319 let angle = Math.atan2(dy, dx) * (180 / Math.PI); 3320 if (angle < 0) angle += 360; 3321 if (angle > 90 && angle <= 270) angle += 180; 3322 const isMovingClockwise = angle >= this.lastAngle; 3323 this.lastAngle = angle; 3324 let startAngle = isMovingClockwise ? angle - 10 : angle + 10; 3325 const distance = Math.sqrt(dx * dx + dy * dy); 3326 if (distance !== 0) { 3327 dx /= distance; 3328 dy /= distance; 3329 } 3330 dx *= distance / 150; 3331 dy *= distance / 150; 3332 3333 ++this.zIndexVal; 3334 this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0; 3335 const img = this.images[this.imgPosition]; 3336 gsap.killTweensOf(img.DOM.el); 3337 3338 gsap
3339 .timeline({ 3340 onStart: () => this.onImageActivated(), 3341 onComplete: () => this.onImageDeactivated() 3342 }) 3343 .fromTo( 3344 img.DOM.el, 3345 { 3346 opacity: 1, 3347 filter: 'brightness(80%)', 3348 scale: 0.1, 3349 zIndex: this.zIndexVal, 3350 x: this.cacheMousePos.x - (img.rect?.width ?? 0) / 2, 3351 y: this.cacheMousePos.y - (img.rect?.height ?? 0) / 2, 3352 rotation: startAngle 3353 }, 3354 { 3355 duration: 1, 3356 ease: 'power2', 3357 scale: 1, 3358 filter: 'brightness(100%)', 3359 x: this.mousePos.x - (img.rect?.width ?? 0) / 2 + dx * 70, 3360 y: this.mousePos.y - (img.rect?.height ?? 0) / 2 + dy * 70, 3361 rotation: this.lastAngle 3362 }, 3363 0 3364 ) 3365 .to( 3366 img.DOM.el, 3367 { 3368 duration: 0.4, 3369 ease: 'expo', 3370 opacity: 0 3371 }, 3372 0.5 3373 ) 3374 .to( 3375 img.DOM.el, 3376 { 3377 duration: 1.5, 3378 ease: 'power4', 3379 x: \`+=\${dx * 120}\`, 3380 y: \`+=\${dy * 120}\` 3381 }, 3382 0.05 3383 ); 3384 } 3385 3386 public destroy() { 3387 this.destroyed = true; 3388 if (this.rafId !== null) { 3389 cancelAnimationFrame(this.rafId); 3390 this.rafId = null; 3391 } 3392 this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener); 3393 this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener); 3394 this.container.removeEventListener('mousemove', this.initRender as EventListener); 3395 this.container.removeEventListener('touchmove', this.initRender as EventListener); 3396 this.images.forEach(img => { 3397 gsap.killTweensOf(img.DOM.el); 3398 img.destroy(); 3399 }); 3400 } 3401 3402 private onImageActivated() { 3403 this.activeImagesCount++; 3404 this.isIdle = false; 3405 } 3406 3407 private onImageDeactivated() { 3408 this.activeImagesCount--; 3409 if (this.activeImagesCount === 0) this.isIdle = true; 3410 } 3411} 3412 3413class ImageTrailVariant6 { 3414 private container: HTMLDivElement; 3415 private DOM: { el: HTMLDivElement }; 3416 private images: ImageItem[]; 3417 private imagesTotal: number; 3418 private imgPosition: number; 3419 private zIndexVal: number; 3420 private activeImagesCount: number; 3421 private isIdle: boolean; 3422 private threshold: number; 3423 private mousePos: { x: number; y: number }; 3424 private lastMousePos: { x: number; y: number }; 3425 private cacheMousePos: { x: number; y: number }; 3426 private rafId: number | null = null; 3427 private destroyed = false; 3428 private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void; 3429 private initRender!: (ev: MouseEvent | TouchEvent) => void; 3430 3431 constructor(container: HTMLDivElement) { 3432 this.container = container; 3433 this.DOM = { el: container }; 3434 this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img as HTMLDivElement)); 3435 this.imagesTotal = this.images.length; 3436 this.imgPosition = 0; 3437 this.zIndexVal = 1; 3438 this.activeImagesCount = 0; 3439 this.isIdle = true; 3440 this.threshold = 80; 3441 this.mousePos = { x: 0, y: 0 }; 3442 this.lastMousePos = { x: 0, y: 0 }; 3443 this.cacheMousePos = { x: 0, y: 0 }; 3444 3445 const handlePointerMove = (ev: MouseEvent | TouchEvent) => { 3446 const rect = container.getBoundingClientRect(); 3447 this.mousePos = getLocalPointerPos(ev, rect); 3448 }; 3449 container.addEventListener('mousemove', handlePointerMove); 3450 container.addEventListener('touchmove', handlePointerMove); 3451 3452 const initRender = (ev: MouseEvent | TouchEvent) => { 3453 const rect = container.getBoundingClientRect(); 3454 this.mousePos = getLocalPointerPos(ev, rect); 3455 this.cacheMousePos = { ...this.mousePos }; 3456 this.rafId = requestAnimationFrame(() => this.render()); 3457 container.removeEventListener('mousemove', initRender as EventListener); 3458 container.removeEventListener('touchmove', initRender as EventListener); 3459 }; 3460 container.addEventListener('mousemove', initRender as EventListener); 3461 container.addEventListener('touchmove', initRender as EventListener); 3462 this.handlePointerMove = handlePointerMove; 3463 this.initRender = initRender; 3464 } 3465 3466 private render() { 3467 if (this.destroyed) return; 3468 3469 const distance = getMouseDistance(this.mousePos, this.lastMousePos); 3470 this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.3); 3471 this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.3); 3472 3473 if (distance > this.threshold) { 3474 this.showNextImage(); 3475 this.lastMousePos = { ...this.mousePos }; 3476 } 3477 if (this.isIdle && this.zIndexVal !== 1) { 3478 this.zIndexVal = 1; 3479 } 3480 this.rafId = requestAnimationFrame(() => this.render()); 3481 } 3482 3483 private mapSpeedToSize(speed: number, minSize: number, maxSize: number) { 3484 const maxSpeed = 200; 3485 return minSize + (maxSize - minSize) * Math.min(speed / maxSpeed, 1); 3486 } 3487 3488 private mapSpeedToBrightness(speed: number, minB: number, maxB: number) { 3489 const maxSpeed = 70; 3490 return minB + (maxB - minB) * Math.min(speed / maxSpeed, 1); 3491 } 3492 3493 private mapSpeedToBlur(speed: number, minBlur: number, maxBlur: number) { 3494 const maxSpeed = 90; 3495 return minBlur + (maxBlur - minBlur) * Math.min(speed / maxSpeed, 1); 3496 } 3497 3498 private mapSpeedToGrayscale(speed: number, minG: number, maxG: number) { 3499 const maxSpeed = 90; 3500 return minG + (maxG - minG) * Math.min(speed / maxSpeed, 1); 3501 } 3502 3503 private showNextImage() { 3504 const dx = this.mousePos.x - this.cacheMousePos.x; 3505 const dy = this.mousePos.y - this.cacheMousePos.y; 3506 const speed = Math.sqrt(dx * dx + dy * dy); 3507 3508 ++this.zIndexVal; 3509 this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0; 3510 const img = this.images[this.imgPosition]; 3511 3512 const scaleFactor = this.mapSpeedToSize(speed, 0.3, 2); 3513 const brightnessValue = this.mapSpeedToBrightness(speed, 0, 1.3); 3514 const blurValue = this.mapSpeedToBlur(speed, 20, 0); 3515 const grayscaleValue = this.mapSpeedToGrayscale(speed, 600, 0); 3516 3517 gsap.killTweensOf(img.DOM.el); 3518 gsap
3519 .timeline({ 3520 onStart: () => this.onImageActivated(), 3521 onComplete: () => this.onImageDeactivated() 3522 }) 3523 .fromTo( 3524 img.DOM.el, 3525 { 3526 opacity: 1, 3527 scale: 0, 3528 zIndex: this.zIndexVal, 3529 x: this.cacheMousePos.x - (img.rect?.width ?? 0) / 2, 3530 y: this.cacheMousePos.y - (img.rect?.height ?? 0) / 2 3531 }, 3532 { 3533 duration: 0.8, 3534 ease: 'power3', 3535 scale: scaleFactor, 3536 filter: \`grayscale(\${grayscaleValue * 100}%) brightness(\${brightnessValue * 100}%) blur(\${blurValue}px)\`, 3537 x: this.mousePos.x - (img.rect?.width ?? 0) / 2, 3538 y: this.mousePos.y - (img.rect?.height ?? 0) / 2 3539 }, 3540 0 3541 ) 3542 .fromTo( 3543 img.DOM.inner, 3544 { scale: 2 }, 3545 { 3546 duration: 0.8, 3547 ease: 'power3', 3548 scale: 1 3549 }, 3550 0 3551 ) 3552 .to( 3553 img.DOM.el, 3554 { 3555 duration: 0.4, 3556 ease: 'power3.in', 3557 opacity: 0, 3558 scale: 0.2 3559 }, 3560 0.45 3561 ); 3562 } 3563 3564 public destroy() { 3565 this.destroyed = true; 3566 if (this.rafId !== null) { 3567 cancelAnimationFrame(this.rafId); 3568 this.rafId = null; 3569 } 3570 this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener); 3571 this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener); 3572 this.container.removeEventListener('mousemove', this.initRender as EventListener); 3573 this.container.removeEventListener('touchmove', this.initRender as EventListener); 3574 this.images.forEach(img => { 3575 gsap.killTweensOf(img.DOM.el); 3576 img.destroy(); 3577 }); 3578 } 3579 3580 private onImageActivated() { 3581 this.activeImagesCount++; 3582 this.isIdle = false; 3583 } 3584 3585 private onImageDeactivated() { 3586 this.activeImagesCount--; 3587 if (this.activeImagesCount === 0) { 3588 this.isIdle = true; 3589 } 3590 } 3591} 3592 3593function getNewPosition(position: number, offset: number, arr: ImageItem[]) { 3594 const realOffset = Math.abs(offset) % arr.length; 3595 if (position - realOffset >= 0) { 3596 return position - realOffset; 3597 } else { 3598 return arr.length - (realOffset - position); 3599 } 3600} 3601 3602class ImageTrailVariant7 { 3603 private container: HTMLDivElement; 3604 private DOM: { el: HTMLDivElement }; 3605 private images: ImageItem[]; 3606 private imagesTotal: number; 3607 private imgPosition: number; 3608 private zIndexVal: number; 3609 private activeImagesCount: number; 3610 private isIdle: boolean; 3611 private threshold: number; 3612 private mousePos: { x: number; y: number }; 3613 private lastMousePos: { x: number; y: number }; 3614 private cacheMousePos: { x: number; y: number }; 3615 private rafId: number | null = null; 3616 private destroyed = false; 3617 private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void; 3618 private initRender!: (ev: MouseEvent | TouchEvent) => void; 3619 private visibleImagesCount: number; 3620 private visibleImagesTotal: number; 3621 3622 constructor(container: HTMLDivElement) { 3623 this.container = container; 3624 this.DOM = { el: container }; 3625 this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img as HTMLDivElement)); 3626 this.imagesTotal = this.images.length; 3627 this.imgPosition = 0; 3628 this.zIndexVal = 1; 3629 this.activeImagesCount = 0; 3630 this.isIdle = true; 3631 this.threshold = 80; 3632 this.mousePos = { x: 0, y: 0 }; 3633 this.lastMousePos = { x: 0, y: 0 }; 3634 this.cacheMousePos = { x: 0, y: 0 }; 3635 this.visibleImagesCount = 0; 3636 this.visibleImagesTotal = 9; 3637 this.visibleImagesTotal = Math.min(this.visibleImagesTotal, this.imagesTotal - 1); 3638 3639 const handlePointerMove = (ev: MouseEvent | TouchEvent) => { 3640 const rect = container.getBoundingClientRect(); 3641 this.mousePos = getLocalPointerPos(ev, rect); 3642 }; 3643 container.addEventListener('mousemove', handlePointerMove); 3644 container.addEventListener('touchmove', handlePointerMove); 3645 3646 const initRender = (ev: MouseEvent | TouchEvent) => { 3647 const rect = container.getBoundingClientRect(); 3648 this.mousePos = getLocalPointerPos(ev, rect); 3649 this.cacheMousePos = { ...this.mousePos }; 3650 this.rafId = requestAnimationFrame(() => this.render()); 3651 container.removeEventListener('mousemove', initRender as EventListener); 3652 container.removeEventListener('touchmove', initRender as EventListener); 3653 }; 3654 container.addEventListener('mousemove', initRender as EventListener); 3655 container.addEventListener('touchmove', initRender as EventListener); 3656 this.handlePointerMove = handlePointerMove; 3657 this.initRender = initRender; 3658 } 3659 3660 private render() { 3661 if (this.destroyed) return; 3662 3663 const distance = getMouseDistance(this.mousePos, this.lastMousePos); 3664 this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.3); 3665 this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.3); 3666 3667 if (distance > this.threshold) { 3668 this.showNextImage(); 3669 this.lastMousePos = { ...this.mousePos }; 3670 } 3671 if (this.isIdle && this.zIndexVal !== 1) this.zIndexVal = 1; 3672 3673 this.rafId = requestAnimationFrame(() => this.render()); 3674 } 3675 3676 private showNextImage() { 3677 ++this.zIndexVal; 3678 this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0; 3679 const img = this.images[this.imgPosition]; 3680 ++this.visibleImagesCount; 3681 3682 gsap.killTweensOf(img.DOM.el); 3683 const scaleValue = gsap.utils.random(0.5, 1.6); 3684 3685 gsap
3686 .timeline({ 3687 onStart: () => this.onImageActivated(), 3688 onComplete: () => this.onImageDeactivated() 3689 }) 3690 .fromTo( 3691 img.DOM.el, 3692 { 3693 scale: scaleValue - Math.max(gsap.utils.random(0.2, 0.6), 0), 3694 rotationZ: 0, 3695 opacity: 1, 3696 zIndex: this.zIndexVal, 3697 x: this.cacheMousePos.x - (img.rect?.width ?? 0) / 2, 3698 y: this.cacheMousePos.y - (img.rect?.height ?? 0) / 2 3699 }, 3700 { 3701 duration: 0.4, 3702 ease: 'power3', 3703 scale: scaleValue, 3704 rotationZ: gsap.utils.random(-3, 3), 3705 x: this.mousePos.x - (img.rect?.width ?? 0) / 2, 3706 y: this.mousePos.y - (img.rect?.height ?? 0) / 2 3707 }, 3708 0 3709 ); 3710 3711 if (this.visibleImagesCount >= this.visibleImagesTotal) { 3712 const lastInQueue = getNewPosition(this.imgPosition, this.visibleImagesTotal, this.images); 3713 const oldImg = this.images[lastInQueue]; 3714 gsap.to(oldImg.DOM.el, { 3715 duration: 0.4, 3716 ease: 'power4', 3717 opacity: 0, 3718 scale: 1.3, 3719 onComplete: () => { 3720 if (this.activeImagesCount === 0) { 3721 this.isIdle = true; 3722 } 3723 } 3724 }); 3725 } 3726 } 3727 3728 public destroy() { 3729 this.destroyed = true; 3730 if (this.rafId !== null) { 3731 cancelAnimationFrame(this.rafId); 3732 this.rafId = null; 3733 } 3734 this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener); 3735 this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener); 3736 this.container.removeEventListener('mousemove', this.initRender as EventListener); 3737 this.container.removeEventListener('touchmove', this.initRender as EventListener); 3738 this.images.forEach(img => { 3739 gsap.killTweensOf(img.DOM.el); 3740 img.destroy(); 3741 }); 3742 } 3743 3744 private onImageActivated() { 3745 this.activeImagesCount++; 3746 this.isIdle = false; 3747 } 3748 3749 private onImageDeactivated() { 3750 this.activeImagesCount--; 3751 } 3752} 3753 3754class ImageTrailVariant8 { 3755 private container: HTMLDivElement; 3756 private DOM: { el: HTMLDivElement }; 3757 private images: ImageItem[]; 3758 private imagesTotal: number; 3759 private imgPosition: number; 3760 private zIndexVal: number; 3761 private activeImagesCount: number; 3762 private isIdle: boolean; 3763 private threshold: number; 3764 private mousePos: { x: number; y: number }; 3765 private lastMousePos: { x: number; y: number }; 3766 private cacheMousePos: { x: number; y: number }; 3767 private rafId: number | null = null; 3768 private destroyed = false; 3769 private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void; 3770 private initRender!: (ev: MouseEvent | TouchEvent) => void; 3771 private rotation: { x: number; y: number }; 3772 private cachedRotation: { x: number; y: number }; 3773 private zValue: number; 3774 private cachedZValue: number; 3775 3776 constructor(container: HTMLDivElement) { 3777 this.container = container; 3778 this.DOM = { el: container }; 3779 this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img as HTMLDivElement)); 3780 this.imagesTotal = this.images.length; 3781 this.imgPosition = 0; 3782 this.zIndexVal = 1; 3783 this.activeImagesCount = 0; 3784 this.isIdle = true; 3785 this.threshold = 80; 3786 this.mousePos = { x: 0, y: 0 }; 3787 this.lastMousePos = { x: 0, y: 0 }; 3788 this.cacheMousePos = { x: 0, y: 0 }; 3789 this.rotation = { x: 0, y: 0 }; 3790 this.cachedRotation = { x: 0, y: 0 }; 3791 this.zValue = 0; 3792 this.cachedZValue = 0; 3793 3794 const handlePointerMove = (ev: MouseEvent | TouchEvent) => { 3795 const rect = container.getBoundingClientRect(); 3796 this.mousePos = getLocalPointerPos(ev, rect); 3797 }; 3798 container.addEventListener('mousemove', handlePointerMove); 3799 container.addEventListener('touchmove', handlePointerMove); 3800 3801 const initRender = (ev: MouseEvent | TouchEvent) => { 3802 const rect = container.getBoundingClientRect(); 3803 this.mousePos = getLocalPointerPos(ev, rect); 3804 this.cacheMousePos = { ...this.mousePos }; 3805 this.rafId = requestAnimationFrame(() => this.render()); 3806 container.removeEventListener('mousemove', initRender as EventListener); 3807 container.removeEventListener('touchmove', initRender as EventListener); 3808 }; 3809 container.addEventListener('mousemove', initRender as EventListener); 3810 container.addEventListener('touchmove', initRender as EventListener); 3811 this.handlePointerMove = handlePointerMove; 3812 this.initRender = initRender; 3813 } 3814 3815 private render() { 3816 if (this.destroyed) return; 3817 3818 const distance = getMouseDistance(this.mousePos, this.lastMousePos); 3819 this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); 3820 this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); 3821 3822 if (distance > this.threshold) { 3823 this.showNextImage(); 3824 this.lastMousePos = { ...this.mousePos }; 3825 } 3826 if (this.isIdle && this.zIndexVal !== 1) { 3827 this.zIndexVal = 1; 3828 } 3829 this.rafId = requestAnimationFrame(() => this.render()); 3830 } 3831 3832 private showNextImage() { 3833 const rect = this.container.getBoundingClientRect(); 3834 const centerX = rect.width / 2; 3835 const centerY = rect.height / 2; 3836 const relX = this.mousePos.x - centerX; 3837 const relY = this.mousePos.y - centerY; 3838 3839 this.rotation.x = -(relY / centerY) * 30; 3840 this.rotation.y = (relX / centerX) * 30; 3841 this.cachedRotation = { ...this.rotation }; 3842 3843 const distanceFromCenter = Math.sqrt(relX * relX + relY * relY); 3844 const maxDistance = Math.sqrt(centerX * centerX + centerY * centerY); 3845 const proportion = distanceFromCenter / maxDistance; 3846 this.zValue = proportion * 1200 - 600; 3847 this.cachedZValue = this.zValue; 3848 const normalizedZ = (this.zValue + 600) / 1200; 3849 const brightness = 0.2 + normalizedZ * 2.3; 3850 3851 ++this.zIndexVal; 3852 this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0; 3853 const img = this.images[this.imgPosition]; 3854 gsap.killTweensOf(img.DOM.el); 3855 3856 gsap
3857 .timeline({ 3858 onStart: () => this.onImageActivated(), 3859 onComplete: () => this.onImageDeactivated() 3860 }) 3861 .set(this.DOM.el, { perspective: 1000 }, 0) 3862 .fromTo( 3863 img.DOM.el, 3864 { 3865 opacity: 1, 3866 z: 0, 3867 scale: 1 + this.cachedZValue / 1000, 3868 zIndex: this.zIndexVal, 3869 x: this.cacheMousePos.x - (img.rect?.width ?? 0) / 2, 3870 y: this.cacheMousePos.y - (img.rect?.height ?? 0) / 2, 3871 rotationX: this.cachedRotation.x, 3872 rotationY: this.cachedRotation.y, 3873 filter: \`brightness(\${brightness})\` 3874 }, 3875 { 3876 duration: 1, 3877 ease: 'expo', 3878 scale: 1 + this.zValue / 1000, 3879 x: this.mousePos.x - (img.rect?.width ?? 0) / 2, 3880 y: this.mousePos.y - (img.rect?.height ?? 0) / 2, 3881 rotationX: this.rotation.x, 3882 rotationY: this.rotation.y 3883 }, 3884 0 3885 ) 3886 .to( 3887 img.DOM.el, 3888 { 3889 duration: 0.4, 3890 ease: 'power2', 3891 opacity: 0, 3892 z: -800 3893 }, 3894 0.3 3895 ); 3896 } 3897 3898 public destroy() { 3899 this.destroyed = true; 3900 if (this.rafId !== null) { 3901 cancelAnimationFrame(this.rafId); 3902 this.rafId = null; 3903 } 3904 this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener); 3905 this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener); 3906 this.container.removeEventListener('mousemove', this.initRender as EventListener); 3907 this.container.removeEventListener('touchmove', this.initRender as EventListener); 3908 this.images.forEach(img => { 3909 gsap.killTweensOf(img.DOM.el); 3910 img.destroy(); 3911 }); 3912 } 3913 3914 private onImageActivated() { 3915 this.activeImagesCount++; 3916 this.isIdle = false; 3917 } 3918 3919 private onImageDeactivated() { 3920 this.activeImagesCount--; 3921 if (this.activeImagesCount === 0) { 3922 this.isIdle = true; 3923 } 3924 } 3925} 3926 3927type ImageTrailConstructor = 3928 | typeof ImageTrailVariant1 3929 | typeof ImageTrailVariant2 3930 | typeof ImageTrailVariant3 3931 | typeof ImageTrailVariant4 3932 | typeof ImageTrailVariant5 3933 | typeof ImageTrailVariant6 3934 | typeof ImageTrailVariant7 3935 | typeof ImageTrailVariant8; 3936 3937const variantMap: Record<number, ImageTrailConstructor> = { 3938 1: ImageTrailVariant1, 3939 2: ImageTrailVariant2, 3940 3: ImageTrailVariant3, 3941 4: ImageTrailVariant4, 3942 5: ImageTrailVariant5, 3943 6: ImageTrailVariant6, 3944 7: ImageTrailVariant7, 3945 8: ImageTrailVariant8 3946}; 3947 3948interface ImageTrailProps { 3949 items?: string[]; 3950 variant?: number; 3951} 3952 3953export default function ImageTrail({ items = [], variant = 1 }: ImageTrailProps): JSX.Element { 3954 const containerRef = useRef<HTMLDivElement>(null); 3955 3956 useEffect(() => { 3957 if (!containerRef.current) return; 3958 const Cls = variantMap[variant] || variantMap[1]; 3959 const instance = new Cls(containerRef.current); 3960 3961 return () => { 3962 instance.destroy(); 3963 }; 3964 // eslint-disable-next-line react-hooks/exhaustive-deps 3965 }, [variant, items]); 3966 3967 return ( 3968 <div className="content" ref={containerRef}> 3969 {items.map((url, i) => ( 3970 <div className="content__img" key={i}> 3971 <div className="content__img-inner" style={{ backgroundImage: \`url(\${url})\` }} /> 3972 </div> 3973 ))} 3974 </div> 3975 ); 3976} 3977`,C=`'use client'; 3978 3979import { gsap } from 'gsap'; 3980import { type JSX, useEffect, useRef } from 'react'; 3981 3982function lerp(a: number, b: number, n: number): number { 3983 return (1 - n) * a + n * b; 3984} 3985 3986function getLocalPointerPos(e: MouseEvent | TouchEvent, rect: DOMRect): { x: number; y: number } { 3987 let clientX = 0, 3988 clientY = 0; 3989 if ('touches' in e && e.touches.length > 0) { 3990 clientX = e.touches[0].clientX; 3991 clientY = e.touches[0].clientY; 3992 } else if ('clientX' in e) { 3993 clientX = e.clientX; 3994 clientY = e.clientY; 3995 } 3996 return { 3997 x: clientX - rect.left, 3998 y: clientY - rect.top 3999 }; 4000} 4001 4002function getMouseDistance(p1: { x: number; y: number }, p2: { x: number; y: number }): number { 4003 const dx = p1.x - p2.x; 4004 const dy = p1.y - p2.y; 4005 return Math.hypot(dx, dy); 4006} 4007 4008class ImageItem { 4009 public DOM: { el: HTMLDivElement; inner: HTMLDivElement | null } = { 4010 el: null as unknown as HTMLDivElement, 4011 inner: null 4012 }; 4013 public defaultStyle: gsap.TweenVars = { scale: 1, x: 0, y: 0, opacity: 0 };
4014 public rect: DOMRect | null = null; 4015 private resize!: () => void; 4016 4017 constructor(DOM_el: HTMLDivElement) { 4018 this.DOM.el = DOM_el; 4019 this.DOM.inner = this.DOM.el.querySelector('.content__img-inner'); 4020 this.getRect(); 4021 this.initEvents(); 4022 } 4023 4024 private initEvents() { 4025 this.resize = () => { 4026 gsap.set(this.DOM.el, this.defaultStyle); 4027 this.getRect(); 4028 }; 4029 window.addEventListener('resize', this.resize); 4030 } 4031 4032 private getRect() { 4033 this.rect = this.DOM.el.getBoundingClientRect(); 4034 } 4035 4036 public destroy() { 4037 window.removeEventListener('resize', this.resize); 4038 } 4039} 4040 4041class ImageTrailVariant1 { 4042 private container: HTMLDivElement; 4043 private DOM: { el: HTMLDivElement }; 4044 private images: ImageItem[]; 4045 private imagesTotal: number; 4046 private imgPosition: number; 4047 private zIndexVal: number; 4048 private activeImagesCount: number; 4049 private isIdle: boolean; 4050 private threshold: number; 4051 private mousePos: { x: number; y: number }; 4052 private lastMousePos: { x: number; y: number }; 4053 private cacheMousePos: { x: number; y: number }; 4054 private rafId: number | null = null; 4055 private destroyed = false; 4056 private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void; 4057 private initRender!: (ev: MouseEvent | TouchEvent) => void; 4058 4059 constructor(container: HTMLDivElement) { 4060 this.container = container; 4061 this.DOM = { el: container }; 4062 this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img as HTMLDivElement)); 4063 this.imagesTotal = this.images.length; 4064 this.imgPosition = 0; 4065 this.zIndexVal = 1; 4066 this.activeImagesCount = 0; 4067 this.isIdle = true; 4068 this.threshold = 80; 4069 this.mousePos = { x: 0, y: 0 }; 4070 this.lastMousePos = { x: 0, y: 0 }; 4071 this.cacheMousePos = { x: 0, y: 0 }; 4072 4073 const handlePointerMove = (ev: MouseEvent | TouchEvent) => { 4074 const rect = this.container.getBoundingClientRect(); 4075 this.mousePos = getLocalPointerPos(ev, rect); 4076 }; 4077 container.addEventListener('mousemove', handlePointerMove); 4078 container.addEventListener('touchmove', handlePointerMove); 4079 4080 const initRender = (ev: MouseEvent | TouchEvent) => { 4081 const rect = this.container.getBoundingClientRect(); 4082 this.mousePos = getLocalPointerPos(ev, rect); 4083 this.cacheMousePos = { ...this.mousePos }; 4084 this.rafId = requestAnimationFrame(() => this.render()); 4085 container.removeEventListener('mousemove', initRender as EventListener); 4086 container.removeEventListener('touchmove', initRender as EventListener); 4087 }; 4088 container.addEventListener('mousemove', initRender as EventListener); 4089 container.addEventListener('touchmove', initRender as EventListener); 4090 this.handlePointerMove = handlePointerMove; 4091 this.initRender = initRender; 4092 } 4093 4094 private render() { 4095 if (this.destroyed) return; 4096 4097 const distance = getMouseDistance(this.mousePos, this.lastMousePos); 4098 this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); 4099 this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); 4100 4101 if (distance > this.threshold) { 4102 this.showNextImage(); 4103 this.lastMousePos = { ...this.mousePos }; 4104 } 4105 if (this.isIdle && this.zIndexVal !== 1) { 4106 this.zIndexVal = 1; 4107 } 4108 this.rafId = requestAnimationFrame(() => this.render()); 4109 } 4110 4111 private showNextImage() { 4112 ++this.zIndexVal; 4113 this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0; 4114 const img = this.images[this.imgPosition]; 4115 4116 gsap.killTweensOf(img.DOM.el); 4117 gsap 4118 .timeline({ 4119 onStart: () => this.onImageActivated(), 4120 onComplete: () => this.onImageDeactivated() 4121 }) 4122 .fromTo( 4123 img.DOM.el, 4124 { 4125 opacity: 1, 4126 scale: 1, 4127 zIndex: this.zIndexVal, 4128 x: this.cacheMousePos.x - (img.rect?.width ?? 0) / 2, 4129 y: this.cacheMousePos.y - (img.rect?.height ?? 0) / 2 4130 }, 4131 { 4132 duration: 0.4, 4133 ease: 'power1', 4134 x: this.mousePos.x - (img.rect?.width ?? 0) / 2, 4135 y: this.mousePos.y - (img.rect?.height ?? 0) / 2 4136 }, 4137 0 4138 ) 4139 .to( 4140 img.DOM.el, 4141 { 4142 duration: 0.4, 4143 ease: 'power3', 4144 opacity: 0, 4145 scale: 0.2 4146 }, 4147 0.4 4148 ); 4149 } 4150
4151 public destroy() { 4152 this.destroyed = true; 4153 if (this.rafId !== null) { 4154 cancelAnimationFrame(this.rafId); 4155 this.rafId = null; 4156 } 4157 this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener); 4158 this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener); 4159 this.container.removeEventListener('mousemove', this.initRender as EventListener); 4160 this.container.removeEventListener('touchmove', this.initRender as EventListener); 4161 this.images.forEach(img => { 4162 gsap.killTweensOf(img.DOM.el); 4163 img.destroy(); 4164 }); 4165 } 4166 4167 private onImageActivated() { 4168 this.activeImagesCount++; 4169 this.isIdle = false; 4170 } 4171 4172 private onImageDeactivated() { 4173 this.activeImagesCount--; 4174 if (this.activeImagesCount === 0) { 4175 this.isIdle = true; 4176 } 4177 } 4178} 4179 4180class ImageTrailVariant2 { 4181 private container: HTMLDivElement; 4182 private DOM: { el: HTMLDivElement }; 4183 private images: ImageItem[]; 4184 private imagesTotal: number; 4185 private imgPosition: number; 4186 private zIndexVal: number; 4187 private activeImagesCount: number; 4188 private isIdle: boolean; 4189 private threshold: number; 4190 private mousePos: { x: number; y: number }; 4191 private lastMousePos: { x: number; y: number }; 4192 private cacheMousePos: { x: number; y: number }; 4193 private rafId: number | null = null; 4194 private destroyed = false; 4195 private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void; 4196 private initRender!: (ev: MouseEvent | TouchEvent) => void; 4197 4198 constructor(container: HTMLDivElement) { 4199 this.container = container; 4200 this.DOM = { el: container }; 4201 this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img as HTMLDivElement)); 4202 this.imagesTotal = this.images.length; 4203 this.imgPosition = 0; 4204 this.zIndexVal = 1; 4205 this.activeImagesCount = 0; 4206 this.isIdle = true; 4207 this.threshold = 80; 4208 this.mousePos = { x: 0, y: 0 }; 4209 this.lastMousePos = { x: 0, y: 0 }; 4210 this.cacheMousePos = { x: 0, y: 0 }; 4211 4212 const handlePointerMove = (ev: MouseEvent | TouchEvent) => { 4213 const rect = container.getBoundingClientRect(); 4214 this.mousePos = getLocalPointerPos(ev, rect); 4215 }; 4216 container.addEventListener('mousemove', handlePointerMove); 4217 container.addEventListener('touchmove', handlePointerMove); 4218 4219 const initRender = (ev: MouseEvent | TouchEvent) => { 4220 const rect = container.getBoundingClientRect(); 4221 this.mousePos = getLocalPointerPos(ev, rect); 4222 this.cacheMousePos = { ...this.mousePos }; 4223 this.rafId = requestAnimationFrame(() => this.render()); 4224 container.removeEventListener('mousemove', initRender as EventListener); 4225 container.removeEventListener('touchmove', initRender as EventListener); 4226 }; 4227 container.addEventListener('mousemove', initRender as EventListener); 4228 container.addEventListener('touchmove', initRender as EventListener); 4229 this.handlePointerMove = handlePointerMove; 4230 this.initRender = initRender; 4231 } 4232 4233 private render() { 4234 if (this.destroyed) return; 4235 4236 const distance = getMouseDistance(this.mousePos, this.lastMousePos); 4237 this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); 4238 this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); 4239 4240 if (distance > this.threshold) { 4241 this.showNextImage(); 4242 this.lastMousePos = { ...this.mousePos }; 4243 } 4244 if (this.isIdle && this.zIndexVal !== 1) { 4245 this.zIndexVal = 1; 4246 } 4247 this.rafId = requestAnimationFrame(() => this.render()); 4248 } 4249 4250 private showNextImage() { 4251 ++this.zIndexVal; 4252 this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0; 4253 const img = this.images[this.imgPosition]; 4254 4255 gsap.killTweensOf(img.DOM.el); 4256 gsap 4257 .timeline({ 4258 onStart: () => this.onImageActivated(), 4259 onComplete: () => this.onImageDeactivated() 4260 }) 4261 .fromTo( 4262 img.DOM.el, 4263 { 4264 opacity: 1, 4265 scale: 0, 4266 zIndex: this.zIndexVal, 4267 x: this.cacheMousePos.x - (img.rect?.width ?? 0) / 2, 4268 y: this.cacheMousePos.y - (img.rect?.height ?? 0) / 2 4269 }, 4270 { 4271 duration: 0.4, 4272 ease: 'power1', 4273 scale: 1, 4274 x: this.mousePos.x - (img.rect?.width ?? 0) / 2, 4275 y: this.mousePos.y - (img.rect?.height ?? 0) / 2 4276 }, 4277 0 4278 ) 4279 .fromTo( 4280 img.DOM.inner, 4281 { scale: 2.8, filter: 'brightness(250%)' }, 4282 { 4283 duration: 0.4, 4284 ease: 'power1', 4285 scale: 1, 4286 filter: 'brightness(100%)' 4287 }, 4288 0 4289 ) 4290 .to( 4291 img.DOM.el, 4292 { 4293 duration: 0.4, 4294 ease: 'power2', 4295 opacity: 0, 4296 scale: 0.2 4297 }, 4298 0.45 4299 ); 4300 } 4301
4302 public destroy() { 4303 this.destroyed = true; 4304 if (this.rafId !== null) { 4305 cancelAnimationFrame(this.rafId); 4306 this.rafId = null; 4307 } 4308 this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener); 4309 this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener); 4310 this.container.removeEventListener('mousemove', this.initRender as EventListener); 4311 this.container.removeEventListener('touchmove', this.initRender as EventListener); 4312 this.images.forEach(img => { 4313 gsap.killTweensOf(img.DOM.el); 4314 img.destroy(); 4315 }); 4316 } 4317 4318 private onImageActivated() { 4319 this.activeImagesCount++; 4320 this.isIdle = false; 4321 } 4322 4323 private onImageDeactivated() { 4324 this.activeImagesCount--; 4325 if (this.activeImagesCount === 0) { 4326 this.isIdle = true; 4327 } 4328 } 4329} 4330 4331class ImageTrailVariant3 { 4332 private container: HTMLDivElement; 4333 private DOM: { el: HTMLDivElement }; 4334 private images: ImageItem[]; 4335 private imagesTotal: number; 4336 private imgPosition: number; 4337 private zIndexVal: number; 4338 private activeImagesCount: number; 4339 private isIdle: boolean; 4340 private threshold: number; 4341 private mousePos: { x: number; y: number }; 4342 private lastMousePos: { x: number; y: number }; 4343 private cacheMousePos: { x: number; y: number }; 4344 private rafId: number | null = null; 4345 private destroyed = false; 4346 private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void; 4347 private initRender!: (ev: MouseEvent | TouchEvent) => void; 4348 4349 constructor(container: HTMLDivElement) { 4350 this.container = container; 4351 this.DOM = { el: container }; 4352 this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img as HTMLDivElement)); 4353 this.imagesTotal = this.images.length; 4354 this.imgPosition = 0; 4355 this.zIndexVal = 1; 4356 this.activeImagesCount = 0; 4357 this.isIdle = true; 4358 this.threshold = 80; 4359 this.mousePos = { x: 0, y: 0 }; 4360 this.lastMousePos = { x: 0, y: 0 }; 4361 this.cacheMousePos = { x: 0, y: 0 }; 4362 4363 const handlePointerMove = (ev: MouseEvent | TouchEvent) => { 4364 const rect = container.getBoundingClientRect(); 4365 this.mousePos = getLocalPointerPos(ev, rect); 4366 }; 4367 container.addEventListener('mousemove', handlePointerMove); 4368 container.addEventListener('touchmove', handlePointerMove); 4369 4370 const initRender = (ev: MouseEvent | TouchEvent) => { 4371 const rect = container.getBoundingClientRect(); 4372 this.mousePos = getLocalPointerPos(ev, rect); 4373 this.cacheMousePos = { ...this.mousePos }; 4374 this.rafId = requestAnimationFrame(() => this.render()); 4375 container.removeEventListener('mousemove', initRender as EventListener); 4376 container.removeEventListener('touchmove', initRender as EventListener); 4377 }; 4378 container.addEventListener('mousemove', initRender as EventListener); 4379 container.addEventListener('touchmove', initRender as EventListener); 4380 this.handlePointerMove = handlePointerMove; 4381 this.initRender = initRender; 4382 } 4383 4384 private render() { 4385 if (this.destroyed) return; 4386 4387 const distance = getMouseDistance(this.mousePos, this.lastMousePos); 4388 this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); 4389 this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); 4390 4391 if (distance > this.threshold) { 4392 this.showNextImage(); 4393 this.lastMousePos = { ...this.mousePos }; 4394 } 4395 if (this.isIdle && this.zIndexVal !== 1) { 4396 this.zIndexVal = 1; 4397 } 4398 this.rafId = requestAnimationFrame(() => this.render()); 4399 } 4400 4401 private showNextImage() { 4402 ++this.zIndexVal; 4403 this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0; 4404 const img = this.images[this.imgPosition]; 4405 4406 gsap.killTweensOf(img.DOM.el); 4407 gsap 4408 .timeline({ 4409 onStart: () => this.onImageActivated(), 4410 onComplete: () => this.onImageDeactivated() 4411 }) 4412 .fromTo( 4413 img.DOM.el, 4414 { 4415 opacity: 1, 4416 scale: 0, 4417 zIndex: this.zIndexVal, 4418 xPercent: 0, 4419 yPercent: 0, 4420 x: this.cacheMousePos.x - (img.rect?.width ?? 0) / 2, 4421 y: this.cacheMousePos.y - (img.rect?.height ?? 0) / 2 4422 }, 4423 { 4424 duration: 0.4, 4425 ease: 'power1', 4426 scale: 1, 4427 x: this.mousePos.x - (img.rect?.width ?? 0) / 2, 4428 y: this.mousePos.y - (img.rect?.height ?? 0) / 2 4429 }, 4430 0 4431 ) 4432 .fromTo( 4433 img.DOM.inner, 4434 { scale: 1.2 }, 4435 { 4436 duration: 0.4, 4437 ease: 'power1', 4438 scale: 1 4439 }, 4440 0 4441 ) 4442 .to( 4443 img.DOM.el, 4444 { 4445 duration: 0.6, 4446 ease: 'power2', 4447 opacity: 0, 4448 scale: 0.2, 4449 xPercent: () => gsap.utils.random(-30, 30), 4450 yPercent: -200 4451 }, 4452 0.6 4453 ); 4454 } 4455
4456 public destroy() { 4457 this.destroyed = true; 4458 if (this.rafId !== null) { 4459 cancelAnimationFrame(this.rafId); 4460 this.rafId = null; 4461 } 4462 this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener); 4463 this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener); 4464 this.container.removeEventListener('mousemove', this.initRender as EventListener); 4465 this.container.removeEventListener('touchmove', this.initRender as EventListener); 4466 this.images.forEach(img => { 4467 gsap.killTweensOf(img.DOM.el); 4468 img.destroy(); 4469 }); 4470 } 4471 4472 private onImageActivated() { 4473 this.activeImagesCount++; 4474 this.isIdle = false; 4475 } 4476 4477 private onImageDeactivated() { 4478 this.activeImagesCount--; 4479 if (this.activeImagesCount === 0) { 4480 this.isIdle = true; 4481 } 4482 } 4483} 4484 4485class ImageTrailVariant4 { 4486 private container: HTMLDivElement; 4487 private DOM: { el: HTMLDivElement }; 4488 private images: ImageItem[]; 4489 private imagesTotal: number; 4490 private imgPosition: number; 4491 private zIndexVal: number; 4492 private activeImagesCount: number; 4493 private isIdle: boolean; 4494 private threshold: number; 4495 private mousePos: { x: number; y: number }; 4496 private lastMousePos: { x: number; y: number }; 4497 private cacheMousePos: { x: number; y: number }; 4498 private rafId: number | null = null; 4499 private destroyed = false; 4500 private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void; 4501 private initRender!: (ev: MouseEvent | TouchEvent) => void; 4502 4503 constructor(container: HTMLDivElement) { 4504 this.container = container; 4505 this.DOM = { el: container }; 4506 this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img as HTMLDivElement)); 4507 this.imagesTotal = this.images.length; 4508 this.imgPosition = 0; 4509 this.zIndexVal = 1; 4510 this.activeImagesCount = 0; 4511 this.isIdle = true; 4512 this.threshold = 80; 4513 this.mousePos = { x: 0, y: 0 }; 4514 this.lastMousePos = { x: 0, y: 0 }; 4515 this.cacheMousePos = { x: 0, y: 0 }; 4516 4517 const handlePointerMove = (ev: MouseEvent | TouchEvent) => { 4518 const rect = container.getBoundingClientRect(); 4519 this.mousePos = getLocalPointerPos(ev, rect); 4520 }; 4521 container.addEventListener('mousemove', handlePointerMove); 4522 container.addEventListener('touchmove', handlePointerMove); 4523 4524 const initRender = (ev: MouseEvent | TouchEvent) => { 4525 const rect = container.getBoundingClientRect(); 4526 this.mousePos = getLocalPointerPos(ev, rect); 4527 this.cacheMousePos = { ...this.mousePos }; 4528 this.rafId = requestAnimationFrame(() => this.render()); 4529 container.removeEventListener('mousemove', initRender as EventListener); 4530 container.removeEventListener('touchmove', initRender as EventListener); 4531 }; 4532 container.addEventListener('mousemove', initRender as EventListener); 4533 container.addEventListener('touchmove', initRender as EventListener); 4534 this.handlePointerMove = handlePointerMove; 4535 this.initRender = initRender; 4536 } 4537 4538 private render() { 4539 if (this.destroyed) return; 4540 4541 const distance = getMouseDistance(this.mousePos, this.lastMousePos); 4542 if (distance > this.threshold) { 4543 this.showNextImage(); 4544 this.lastMousePos = { ...this.mousePos }; 4545 } 4546 this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); 4547 this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); 4548 4549 if (this.isIdle && this.zIndexVal !== 1) this.zIndexVal = 1; 4550 this.rafId = requestAnimationFrame(() => this.render()); 4551 } 4552 4553 private showNextImage() { 4554 ++this.zIndexVal; 4555 this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0; 4556 const img = this.images[this.imgPosition]; 4557 gsap.killTweensOf(img.DOM.el); 4558 4559 let dx = this.mousePos.x - this.cacheMousePos.x; 4560 let dy = this.mousePos.y - this.cacheMousePos.y; 4561 let distance = Math.sqrt(dx * dx + dy * dy); 4562 if (distance !== 0) { 4563 dx /= distance; 4564 dy /= distance; 4565 } 4566 dx *= distance / 100; 4567 dy *= distance / 100; 4568 4569 gsap 4570 .timeline({ 4571 onStart: () => this.onImageActivated(), 4572 onComplete: () => this.onImageDeactivated() 4573 }) 4574 .fromTo( 4575 img.DOM.el, 4576 { 4577 opacity: 1, 4578 scale: 0, 4579 zIndex: this.zIndexVal, 4580 x: this.cacheMousePos.x - (img.rect?.width ?? 0) / 2, 4581 y: this.cacheMousePos.y - (img.rect?.height ?? 0) / 2 4582 }, 4583 { 4584 duration: 0.4, 4585 ease: 'power1', 4586 scale: 1, 4587 x: this.mousePos.x - (img.rect?.width ?? 0) / 2, 4588 y: this.mousePos.y - (img.rect?.height ?? 0) / 2 4589 }, 4590 0 4591 ) 4592 .fromTo( 4593 img.DOM.inner, 4594 { 4595 scale: 2, 4596 filter: \`brightness(\${Math.max((400 * distance) / 100, 100)}%) contrast(\${Math.max( 4597 (400 * distance) / 100, 4598 100 4599 )}%)\` 4600 }, 4601 { 4602 duration: 0.4, 4603 ease: 'power1', 4604 scale: 1, 4605 filter: 'brightness(100%) contrast(100%)' 4606 }, 4607 0 4608 ) 4609 .to( 4610 img.DOM.el, 4611 { 4612 duration: 0.4, 4613 ease: 'power3', 4614 opacity: 0 4615 }, 4616 0.4 4617 ) 4618 .to( 4619 img.DOM.el, 4620 { 4621 duration: 1.5, 4622 ease: 'power4', 4623 x: \`+=\${dx * 110}\`, 4624 y: \`+=\${dy * 110}\` 4625 }, 4626 0.05 4627 ); 4628 } 4629
4630 public destroy() { 4631 this.destroyed = true; 4632 if (this.rafId !== null) { 4633 cancelAnimationFrame(this.rafId); 4634 this.rafId = null; 4635 } 4636 this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener); 4637 this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener); 4638 this.container.removeEventListener('mousemove', this.initRender as EventListener); 4639 this.container.removeEventListener('touchmove', this.initRender as EventListener); 4640 this.images.forEach(img => { 4641 gsap.killTweensOf(img.DOM.el); 4642 img.destroy(); 4643 }); 4644 } 4645 4646 private onImageActivated() { 4647 this.activeImagesCount++; 4648 this.isIdle = false; 4649 } 4650 4651 private onImageDeactivated() { 4652 this.activeImagesCount--; 4653 if (this.activeImagesCount === 0) { 4654 this.isIdle = true; 4655 } 4656 } 4657} 4658 4659class ImageTrailVariant5 { 4660 private container: HTMLDivElement; 4661 private DOM: { el: HTMLDivElement }; 4662 private images: ImageItem[]; 4663 private imagesTotal: number; 4664 private imgPosition: number; 4665 private zIndexVal: number; 4666 private activeImagesCount: number; 4667 private isIdle: boolean; 4668 private threshold: number; 4669 private mousePos: { x: number; y: number }; 4670 private lastMousePos: { x: number; y: number }; 4671 private cacheMousePos: { x: number; y: number }; 4672 private rafId: number | null = null; 4673 private destroyed = false; 4674 private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void; 4675 private initRender!: (ev: MouseEvent | TouchEvent) => void; 4676 private lastAngle: number; 4677 4678 constructor(container: HTMLDivElement) { 4679 this.container = container; 4680 this.DOM = { el: container }; 4681 this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img as HTMLDivElement)); 4682 this.imagesTotal = this.images.length; 4683 this.imgPosition = 0; 4684 this.zIndexVal = 1; 4685 this.activeImagesCount = 0; 4686 this.isIdle = true; 4687 this.threshold = 80; 4688 this.mousePos = { x: 0, y: 0 }; 4689 this.lastMousePos = { x: 0, y: 0 }; 4690 this.cacheMousePos = { x: 0, y: 0 }; 4691 this.lastAngle = 0; 4692 4693 const handlePointerMove = (ev: MouseEvent | TouchEvent) => { 4694 const rect = container.getBoundingClientRect(); 4695 this.mousePos = getLocalPointerPos(ev, rect); 4696 }; 4697 container.addEventListener('mousemove', handlePointerMove); 4698 container.addEventListener('touchmove', handlePointerMove); 4699 4700 const initRender = (ev: MouseEvent | TouchEvent) => { 4701 const rect = container.getBoundingClientRect(); 4702 this.mousePos = getLocalPointerPos(ev, rect); 4703 this.cacheMousePos = { ...this.mousePos }; 4704 this.rafId = requestAnimationFrame(() => this.render()); 4705 container.removeEventListener('mousemove', initRender as EventListener); 4706 container.removeEventListener('touchmove', initRender as EventListener); 4707 }; 4708 container.addEventListener('mousemove', initRender as EventListener); 4709 container.addEventListener('touchmove', initRender as EventListener); 4710 this.handlePointerMove = handlePointerMove; 4711 this.initRender = initRender; 4712 } 4713 4714 private render() { 4715 if (this.destroyed) return; 4716 4717 const distance = getMouseDistance(this.mousePos, this.lastMousePos); 4718 if (distance > this.threshold) { 4719 this.showNextImage(); 4720 this.lastMousePos = { ...this.mousePos }; 4721 } 4722 this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); 4723 this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); 4724 if (this.isIdle && this.zIndexVal !== 1) this.zIndexVal = 1; 4725 this.rafId = requestAnimationFrame(() => this.render()); 4726 } 4727 4728 private showNextImage() { 4729 let dx = this.mousePos.x - this.cacheMousePos.x; 4730 let dy = this.mousePos.y - this.cacheMousePos.y; 4731 let angle = Math.atan2(dy, dx) * (180 / Math.PI); 4732 if (angle < 0) angle += 360; 4733 if (angle > 90 && angle <= 270) angle += 180; 4734 const isMovingClockwise = angle >= this.lastAngle; 4735 this.lastAngle = angle; 4736 let startAngle = isMovingClockwise ? angle - 10 : angle + 10; 4737 const distance = Math.sqrt(dx * dx + dy * dy); 4738 if (distance !== 0) { 4739 dx /= distance; 4740 dy /= distance; 4741 } 4742 dx *= distance / 150; 4743 dy *= distance / 150; 4744 4745 ++this.zIndexVal; 4746 this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0; 4747 const img = this.images[this.imgPosition]; 4748 gsap.killTweensOf(img.DOM.el); 4749 4750 gsap
4751 .timeline({ 4752 onStart: () => this.onImageActivated(), 4753 onComplete: () => this.onImageDeactivated() 4754 }) 4755 .fromTo( 4756 img.DOM.el, 4757 { 4758 opacity: 1, 4759 filter: 'brightness(80%)', 4760 scale: 0.1, 4761 zIndex: this.zIndexVal, 4762 x: this.cacheMousePos.x - (img.rect?.width ?? 0) / 2, 4763 y: this.cacheMousePos.y - (img.rect?.height ?? 0) / 2, 4764 rotation: startAngle 4765 }, 4766 { 4767 duration: 1, 4768 ease: 'power2', 4769 scale: 1, 4770 filter: 'brightness(100%)', 4771 x: this.mousePos.x - (img.rect?.width ?? 0) / 2 + dx * 70, 4772 y: this.mousePos.y - (img.rect?.height ?? 0) / 2 + dy * 70, 4773 rotation: this.lastAngle 4774 }, 4775 0 4776 ) 4777 .to( 4778 img.DOM.el, 4779 { 4780 duration: 0.4, 4781 ease: 'expo', 4782 opacity: 0 4783 }, 4784 0.5 4785 ) 4786 .to( 4787 img.DOM.el, 4788 { 4789 duration: 1.5, 4790 ease: 'power4', 4791 x: \`+=\${dx * 120}\`, 4792 y: \`+=\${dy * 120}\` 4793 }, 4794 0.05 4795 ); 4796 } 4797 4798 public destroy() { 4799 this.destroyed = true; 4800 if (this.rafId !== null) { 4801 cancelAnimationFrame(this.rafId); 4802 this.rafId = null; 4803 } 4804 this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener); 4805 this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener); 4806 this.container.removeEventListener('mousemove', this.initRender as EventListener); 4807 this.container.removeEventListener('touchmove', this.initRender as EventListener); 4808 this.images.forEach(img => { 4809 gsap.killTweensOf(img.DOM.el); 4810 img.destroy(); 4811 }); 4812 } 4813 4814 private onImageActivated() { 4815 this.activeImagesCount++; 4816 this.isIdle = false; 4817 } 4818 4819 private onImageDeactivated() { 4820 this.activeImagesCount--; 4821 if (this.activeImagesCount === 0) this.isIdle = true; 4822 } 4823} 4824 4825class ImageTrailVariant6 { 4826 private container: HTMLDivElement; 4827 private DOM: { el: HTMLDivElement }; 4828 private images: ImageItem[]; 4829 private imagesTotal: number; 4830 private imgPosition: number; 4831 private zIndexVal: number; 4832 private activeImagesCount: number; 4833 private isIdle: boolean; 4834 private threshold: number; 4835 private mousePos: { x: number; y: number }; 4836 private lastMousePos: { x: number; y: number }; 4837 private cacheMousePos: { x: number; y: number }; 4838 private rafId: number | null = null; 4839 private destroyed = false; 4840 private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void; 4841 private initRender!: (ev: MouseEvent | TouchEvent) => void; 4842 4843 constructor(container: HTMLDivElement) { 4844 this.container = container; 4845 this.DOM = { el: container }; 4846 this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img as HTMLDivElement)); 4847 this.imagesTotal = this.images.length; 4848 this.imgPosition = 0; 4849 this.zIndexVal = 1; 4850 this.activeImagesCount = 0; 4851 this.isIdle = true; 4852 this.threshold = 80; 4853 this.mousePos = { x: 0, y: 0 }; 4854 this.lastMousePos = { x: 0, y: 0 }; 4855 this.cacheMousePos = { x: 0, y: 0 }; 4856 4857 const handlePointerMove = (ev: MouseEvent | TouchEvent) => { 4858 const rect = container.getBoundingClientRect(); 4859 this.mousePos = getLocalPointerPos(ev, rect); 4860 }; 4861 container.addEventListener('mousemove', handlePointerMove); 4862 container.addEventListener('touchmove', handlePointerMove); 4863 4864 const initRender = (ev: MouseEvent | TouchEvent) => { 4865 const rect = container.getBoundingClientRect(); 4866 this.mousePos = getLocalPointerPos(ev, rect); 4867 this.cacheMousePos = { ...this.mousePos }; 4868 this.rafId = requestAnimationFrame(() => this.render()); 4869 container.removeEventListener('mousemove', initRender as EventListener); 4870 container.removeEventListener('touchmove', initRender as EventListener); 4871 }; 4872 container.addEventListener('mousemove', initRender as EventListener); 4873 container.addEventListener('touchmove', initRender as EventListener); 4874 this.handlePointerMove = handlePointerMove; 4875 this.initRender = initRender; 4876 } 4877 4878 private render() { 4879 if (this.destroyed) return; 4880 4881 const distance = getMouseDistance(this.mousePos, this.lastMousePos); 4882 this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.3); 4883 this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.3); 4884 4885 if (distance > this.threshold) { 4886 this.showNextImage(); 4887 this.lastMousePos = { ...this.mousePos }; 4888 } 4889 if (this.isIdle && this.zIndexVal !== 1) { 4890 this.zIndexVal = 1; 4891 } 4892 this.rafId = requestAnimationFrame(() => this.render()); 4893 } 4894 4895 private mapSpeedToSize(speed: number, minSize: number, maxSize: number) { 4896 const maxSpeed = 200; 4897 return minSize + (maxSize - minSize) * Math.min(speed / maxSpeed, 1); 4898 } 4899 4900 private mapSpeedToBrightness(speed: number, minB: number, maxB: number) { 4901 const maxSpeed = 70; 4902 return minB + (maxB - minB) * Math.min(speed / maxSpeed, 1); 4903 } 4904 4905 private mapSpeedToBlur(speed: number, minBlur: number, maxBlur: number) { 4906 const maxSpeed = 90; 4907 return minBlur + (maxBlur - minBlur) * Math.min(speed / maxSpeed, 1); 4908 } 4909 4910 private mapSpeedToGrayscale(speed: number, minG: number, maxG: number) { 4911 const maxSpeed = 90; 4912 return minG + (maxG - minG) * Math.min(speed / maxSpeed, 1); 4913 } 4914 4915 private showNextImage() { 4916 const dx = this.mousePos.x - this.cacheMousePos.x; 4917 const dy = this.mousePos.y - this.cacheMousePos.y; 4918 const speed = Math.sqrt(dx * dx + dy * dy); 4919 4920 ++this.zIndexVal; 4921 this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0; 4922 const img = this.images[this.imgPosition]; 4923 4924 const scaleFactor = this.mapSpeedToSize(speed, 0.3, 2); 4925 const brightnessValue = this.mapSpeedToBrightness(speed, 0, 1.3); 4926 const blurValue = this.mapSpeedToBlur(speed, 20, 0); 4927 const grayscaleValue = this.mapSpeedToGrayscale(speed, 600, 0); 4928 4929 gsap.killTweensOf(img.DOM.el); 4930 gsap
4931 .timeline({ 4932 onStart: () => this.onImageActivated(), 4933 onComplete: () => this.onImageDeactivated() 4934 }) 4935 .fromTo( 4936 img.DOM.el, 4937 { 4938 opacity: 1, 4939 scale: 0, 4940 zIndex: this.zIndexVal, 4941 x: this.cacheMousePos.x - (img.rect?.width ?? 0) / 2, 4942 y: this.cacheMousePos.y - (img.rect?.height ?? 0) / 2 4943 }, 4944 { 4945 duration: 0.8, 4946 ease: 'power3', 4947 scale: scaleFactor, 4948 filter: \`grayscale(\${grayscaleValue * 100}%) brightness(\${brightnessValue * 100}%) blur(\${blurValue}px)\`, 4949 x: this.mousePos.x - (img.rect?.width ?? 0) / 2, 4950 y: this.mousePos.y - (img.rect?.height ?? 0) / 2 4951 }, 4952 0 4953 ) 4954 .fromTo( 4955 img.DOM.inner, 4956 { scale: 2 }, 4957 { 4958 duration: 0.8, 4959 ease: 'power3', 4960 scale: 1 4961 }, 4962 0 4963 ) 4964 .to( 4965 img.DOM.el, 4966 { 4967 duration: 0.4, 4968 ease: 'power3.in', 4969 opacity: 0, 4970 scale: 0.2 4971 }, 4972 0.45 4973 ); 4974 } 4975 4976 public destroy() { 4977 this.destroyed = true; 4978 if (this.rafId !== null) { 4979 cancelAnimationFrame(this.rafId); 4980 this.rafId = null; 4981 } 4982 this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener); 4983 this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener); 4984 this.container.removeEventListener('mousemove', this.initRender as EventListener); 4985 this.container.removeEventListener('touchmove', this.initRender as EventListener); 4986 this.images.forEach(img => { 4987 gsap.killTweensOf(img.DOM.el); 4988 img.destroy(); 4989 }); 4990 } 4991 4992 private onImageActivated() { 4993 this.activeImagesCount++; 4994 this.isIdle = false; 4995 } 4996 4997 private onImageDeactivated() { 4998 this.activeImagesCount--; 4999 if (this.activeImagesCount === 0) { 5000 this.isIdle = true; 5001 } 5002 } 5003} 5004 5005function getNewPosition(position: number, offset: number, arr: ImageItem[]) { 5006 const realOffset = Math.abs(offset) % arr.length; 5007 if (position - realOffset >= 0) { 5008 return position - realOffset; 5009 } else { 5010 return arr.length - (realOffset - position); 5011 } 5012} 5013 5014class ImageTrailVariant7 { 5015 private container: HTMLDivElement; 5016 private DOM: { el: HTMLDivElement }; 5017 private images: ImageItem[]; 5018 private imagesTotal: number; 5019 private imgPosition: number; 5020 private zIndexVal: number; 5021 private activeImagesCount: number; 5022 private isIdle: boolean; 5023 private threshold: number; 5024 private mousePos: { x: number; y: number }; 5025 private lastMousePos: { x: number; y: number }; 5026 private cacheMousePos: { x: number; y: number }; 5027 private rafId: number | null = null; 5028 private destroyed = false; 5029 private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void; 5030 private initRender!: (ev: MouseEvent | TouchEvent) => void; 5031 private visibleImagesCount: number; 5032 private visibleImagesTotal: number; 5033 5034 constructor(container: HTMLDivElement) { 5035 this.container = container; 5036 this.DOM = { el: container }; 5037 this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img as HTMLDivElement)); 5038 this.imagesTotal = this.images.length; 5039 this.imgPosition = 0; 5040 this.zIndexVal = 1; 5041 this.activeImagesCount = 0; 5042 this.isIdle = true; 5043 this.threshold = 80; 5044 this.mousePos = { x: 0, y: 0 }; 5045 this.lastMousePos = { x: 0, y: 0 }; 5046 this.cacheMousePos = { x: 0, y: 0 }; 5047 this.visibleImagesCount = 0; 5048 this.visibleImagesTotal = 9; 5049 this.visibleImagesTotal = Math.min(this.visibleImagesTotal, this.imagesTotal - 1); 5050 5051 const handlePointerMove = (ev: MouseEvent | TouchEvent) => { 5052 const rect = container.getBoundingClientRect(); 5053 this.mousePos = getLocalPointerPos(ev, rect); 5054 }; 5055 container.addEventListener('mousemove', handlePointerMove); 5056 container.addEventListener('touchmove', handlePointerMove); 5057 5058 const initRender = (ev: MouseEvent | TouchEvent) => { 5059 const rect = container.getBoundingClientRect(); 5060 this.mousePos = getLocalPointerPos(ev, rect); 5061 this.cacheMousePos = { ...this.mousePos }; 5062 this.rafId = requestAnimationFrame(() => this.render()); 5063 container.removeEventListener('mousemove', initRender as EventListener); 5064 container.removeEventListener('touchmove', initRender as EventListener); 5065 }; 5066 container.addEventListener('mousemove', initRender as EventListener); 5067 container.addEventListener('touchmove', initRender as EventListener); 5068 this.handlePointerMove = handlePointerMove; 5069 this.initRender = initRender; 5070 } 5071 5072 private render() { 5073 if (this.destroyed) return; 5074 5075 const distance = getMouseDistance(this.mousePos, this.lastMousePos); 5076 this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.3); 5077 this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.3); 5078 5079 if (distance > this.threshold) { 5080 this.showNextImage(); 5081 this.lastMousePos = { ...this.mousePos }; 5082 } 5083 if (this.isIdle && this.zIndexVal !== 1) this.zIndexVal = 1; 5084 5085 this.rafId = requestAnimationFrame(() => this.render()); 5086 } 5087 5088 private showNextImage() { 5089 ++this.zIndexVal; 5090 this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0; 5091 const img = this.images[this.imgPosition]; 5092 ++this.visibleImagesCount; 5093 5094 gsap.killTweensOf(img.DOM.el); 5095 const scaleValue = gsap.utils.random(0.5, 1.6); 5096 5097 gsap
5098 .timeline({ 5099 onStart: () => this.onImageActivated(), 5100 onComplete: () => this.onImageDeactivated() 5101 }) 5102 .fromTo( 5103 img.DOM.el, 5104 { 5105 scale: scaleValue - Math.max(gsap.utils.random(0.2, 0.6), 0), 5106 rotationZ: 0, 5107 opacity: 1, 5108 zIndex: this.zIndexVal, 5109 x: this.cacheMousePos.x - (img.rect?.width ?? 0) / 2, 5110 y: this.cacheMousePos.y - (img.rect?.height ?? 0) / 2 5111 }, 5112 { 5113 duration: 0.4, 5114 ease: 'power3', 5115 scale: scaleValue, 5116 rotationZ: gsap.utils.random(-3, 3), 5117 x: this.mousePos.x - (img.rect?.width ?? 0) / 2, 5118 y: this.mousePos.y - (img.rect?.height ?? 0) / 2 5119 }, 5120 0 5121 ); 5122 5123 if (this.visibleImagesCount >= this.visibleImagesTotal) { 5124 const lastInQueue = getNewPosition(this.imgPosition, this.visibleImagesTotal, this.images); 5125 const oldImg = this.images[lastInQueue]; 5126 gsap.to(oldImg.DOM.el, { 5127 duration: 0.4, 5128 ease: 'power4', 5129 opacity: 0, 5130 scale: 1.3, 5131 onComplete: () => { 5132 if (this.activeImagesCount === 0) { 5133 this.isIdle = true; 5134 } 5135 } 5136 }); 5137 } 5138 } 5139 5140 public destroy() { 5141 this.destroyed = true; 5142 if (this.rafId !== null) { 5143 cancelAnimationFrame(this.rafId); 5144 this.rafId = null; 5145 } 5146 this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener); 5147 this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener); 5148 this.container.removeEventListener('mousemove', this.initRender as EventListener); 5149 this.container.removeEventListener('touchmove', this.initRender as EventListener); 5150 this.images.forEach(img => { 5151 gsap.killTweensOf(img.DOM.el); 5152 img.destroy(); 5153 }); 5154 } 5155 5156 private onImageActivated() { 5157 this.activeImagesCount++; 5158 this.isIdle = false; 5159 } 5160 5161 private onImageDeactivated() { 5162 this.activeImagesCount--; 5163 } 5164} 5165 5166class ImageTrailVariant8 { 5167 private container: HTMLDivElement; 5168 private DOM: { el: HTMLDivElement }; 5169 private images: ImageItem[]; 5170 private imagesTotal: number; 5171 private imgPosition: number; 5172 private zIndexVal: number; 5173 private activeImagesCount: number; 5174 private isIdle: boolean; 5175 private threshold: number; 5176 private mousePos: { x: number; y: number }; 5177 private lastMousePos: { x: number; y: number }; 5178 private cacheMousePos: { x: number; y: number }; 5179 private rafId: number | null = null; 5180 private destroyed = false; 5181 private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void; 5182 private initRender!: (ev: MouseEvent | TouchEvent) => void; 5183 private rotation: { x: number; y: number }; 5184 private cachedRotation: { x: number; y: number }; 5185 private zValue: number; 5186 private cachedZValue: number; 5187 5188 constructor(container: HTMLDivElement) { 5189 this.container = container; 5190 this.DOM = { el: container }; 5191 this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img as HTMLDivElement)); 5192 this.imagesTotal = this.images.length; 5193 this.imgPosition = 0; 5194 this.zIndexVal = 1; 5195 this.activeImagesCount = 0; 5196 this.isIdle = true; 5197 this.threshold = 80; 5198 this.mousePos = { x: 0, y: 0 }; 5199 this.lastMousePos = { x: 0, y: 0 }; 5200 this.cacheMousePos = { x: 0, y: 0 }; 5201 this.rotation = { x: 0, y: 0 }; 5202 this.cachedRotation = { x: 0, y: 0 }; 5203 this.zValue = 0; 5204 this.cachedZValue = 0; 5205 5206 const handlePointerMove = (ev: MouseEvent | TouchEvent) => { 5207 const rect = container.getBoundingClientRect(); 5208 this.mousePos = getLocalPointerPos(ev, rect); 5209 }; 5210 container.addEventListener('mousemove', handlePointerMove); 5211 container.addEventListener('touchmove', handlePointerMove); 5212 5213 const initRender = (ev: MouseEvent | TouchEvent) => { 5214 const rect = container.getBoundingClientRect(); 5215 this.mousePos = getLocalPointerPos(ev, rect); 5216 this.cacheMousePos = { ...this.mousePos }; 5217 this.rafId = requestAnimationFrame(() => this.render()); 5218 container.removeEventListener('mousemove', initRender as EventListener); 5219 container.removeEventListener('touchmove', initRender as EventListener); 5220 }; 5221 container.addEventListener('mousemove', initRender as EventListener); 5222 container.addEventListener('touchmove', initRender as EventListener); 5223 this.handlePointerMove = handlePointerMove; 5224 this.initRender = initRender; 5225 } 5226 5227 private render() { 5228 if (this.destroyed) return; 5229 5230 const distance = getMouseDistance(this.mousePos, this.lastMousePos); 5231 this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); 5232 this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); 5233 5234 if (distance > this.threshold) { 5235 this.showNextImage(); 5236 this.lastMousePos = { ...this.mousePos }; 5237 } 5238 if (this.isIdle && this.zIndexVal !== 1) { 5239 this.zIndexVal = 1; 5240 } 5241 this.rafId = requestAnimationFrame(() => this.render()); 5242 } 5243 5244 private showNextImage() { 5245 const rect = this.container.getBoundingClientRect(); 5246 const centerX = rect.width / 2; 5247 const centerY = rect.height / 2; 5248 const relX = this.mousePos.x - centerX; 5249 const relY = this.mousePos.y - centerY; 5250 5251 this.rotation.x = -(relY / centerY) * 30; 5252 this.rotation.y = (relX / centerX) * 30; 5253 this.cachedRotation = { ...this.rotation }; 5254 5255 const distanceFromCenter = Math.sqrt(relX * relX + relY * relY); 5256 const maxDistance = Math.sqrt(centerX * centerX + centerY * centerY); 5257 const proportion = distanceFromCenter / maxDistance; 5258 this.zValue = proportion * 1200 - 600; 5259 this.cachedZValue = this.zValue; 5260 const normalizedZ = (this.zValue + 600) / 1200; 5261 const brightness = 0.2 + normalizedZ * 2.3; 5262 5263 ++this.zIndexVal; 5264 this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0; 5265 const img = this.images[this.imgPosition]; 5266 gsap.killTweensOf(img.DOM.el); 5267 5268 gsap
5269 .timeline({ 5270 onStart: () => this.onImageActivated(), 5271 onComplete: () => this.onImageDeactivated() 5272 }) 5273 .set(this.DOM.el, { perspective: 1000 }, 0) 5274 .fromTo( 5275 img.DOM.el, 5276 { 5277 opacity: 1, 5278 z: 0, 5279 scale: 1 + this.cachedZValue / 1000, 5280 zIndex: this.zIndexVal, 5281 x: this.cacheMousePos.x - (img.rect?.width ?? 0) / 2, 5282 y: this.cacheMousePos.y - (img.rect?.height ?? 0) / 2, 5283 rotationX: this.cachedRotation.x, 5284 rotationY: this.cachedRotation.y, 5285 filter: \`brightness(\${brightness})\` 5286 }, 5287 { 5288 duration: 1, 5289 ease: 'expo', 5290 scale: 1 + this.zValue / 1000, 5291 x: this.mousePos.x - (img.rect?.width ?? 0) / 2, 5292 y: this.mousePos.y - (img.rect?.height ?? 0) / 2, 5293 rotationX: this.rotation.x, 5294 rotationY: this.rotation.y 5295 }, 5296 0 5297 ) 5298 .to( 5299 img.DOM.el, 5300 { 5301 duration: 0.4, 5302 ease: 'power2', 5303 opacity: 0, 5304 z: -800 5305 }, 5306 0.3 5307 ); 5308 } 5309 5310 public destroy() { 5311 this.destroyed = true; 5312 if (this.rafId !== null) { 5313 cancelAnimationFrame(this.rafId); 5314 this.rafId = null; 5315 } 5316 this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener); 5317 this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener); 5318 this.container.removeEventListener('mousemove', this.initRender as EventListener); 5319 this.container.removeEventListener('touchmove', this.initRender as EventListener); 5320 this.images.forEach(img => { 5321 gsap.killTweensOf(img.DOM.el); 5322 img.destroy(); 5323 }); 5324 } 5325 5326 private onImageActivated() { 5327 this.activeImagesCount++; 5328 this.isIdle = false; 5329 } 5330 5331 private onImageDeactivated() { 5332 this.activeImagesCount--; 5333 if (this.activeImagesCount === 0) { 5334 this.isIdle = true; 5335 } 5336 } 5337} 5338 5339type ImageTrailConstructor = 5340 | typeof ImageTrailVariant1 5341 | typeof ImageTrailVariant2 5342 | typeof ImageTrailVariant3 5343 | typeof ImageTrailVariant4 5344 | typeof ImageTrailVariant5 5345 | typeof ImageTrailVariant6 5346 | typeof ImageTrailVariant7 5347 | typeof ImageTrailVariant8; 5348 5349const variantMap: Record<number, ImageTrailConstructor> = { 5350 1: ImageTrailVariant1, 5351 2: ImageTrailVariant2, 5352 3: ImageTrailVariant3, 5353 4: ImageTrailVariant4, 5354 5: ImageTrailVariant5, 5355 6: ImageTrailVariant6, 5356 7: ImageTrailVariant7, 5357 8: ImageTrailVariant8 5358}; 5359 5360interface ImageTrailProps { 5361 items?: string[]; 5362 variant?: number; 5363} 5364 5365export default function ImageTrail({ items = [], variant = 1 }: ImageTrailProps): JSX.Element { 5366 const containerRef = useRef<HTMLDivElement>(null); 5367 5368 useEffect(() => { 5369 if (!containerRef.current) return; 5370 const Cls = variantMap[variant] || variantMap[1]; 5371 const instance = new Cls(containerRef.current); 5372 5373 return () => { 5374 instance.destroy(); 5375 }; 5376 // eslint-disable-next-line react-hooks/exhaustive-deps 5377 }, [variant, items]); 5378 5379 return ( 5380 <div className="w-full h-full relative z-[100] rounded-lg bg-transparent overflow-visible" ref={containerRef}> 5381 {items.map((url, i) => ( 5382 <div 5383 className="content__img w-[190px] aspect-[1.1] rounded-[15px] absolute top-0 left-0 opacity-0 overflow-hidden [will-change:transform,filter]" 5384 key={i} 5385 > 5386 <div 5387 className="content__img-inner bg-center bg-cover w-[calc(100%+20px)] h-[calc(100%+20px)] absolute top-[-10px] left-[-10px]" 5388 style={{ backgroundImage: \`url(\${url})\` }} 5389 /> 5390 </div> 5391 ))} 5392 </div> 5393 ); 5394} 5395`,z={dependencies:"gsap",usage:`import ImageTrail from './ImageTrail;' 5396 5397<div style={{ height: '500px', position: 'relative', overflow: 'hidden'}}> 5398 <ImageTrail 5399 key={key} 5400 items={[ 5401 'https://picsum.photos/id/287/300/300', 5402 'https://picsum.photos/id/1001/300/300', 5403 'https://picsum.photos/id/1025/300/300', 5404 'https://picsum.photos/id/1026/300/300', 5405 'https://picsum.photos/id/1027/300/300', 5406 'https://picsum.photos/id/1028/300/300', 5407 'https://picsum.photos/id/1029/300/300', 5408 'https://picsum.photos/id/1030/300/300', 5409 // ... 5410 ]} 5411 variant={1} 5412 /> 5413</div>`,code:D,css:w,tailwind:V,tsCode:O,tsTailwind:C},t={variant:"1"},J=()=>{const{props:i,updateProp:a,resetProps:r,hasChanges:h}=g(t),{variant:n}=i,[m,c]=L(),l=u.useMemo(()=>[{name:"items",type:"string[]",default:"[]",description:"An array of image URLs which will be animated in the trail."},{name:"variant",type:"number",default:"1",description:"A number from 1 to 8 - all different animation styles."}],[]);return e.jsx(P,{props:i,defaultProps:t,resetProps:r,hasChanges:h,children:e.jsxs(p,{children:[e.jsxs(M,{children:[e.jsxs(v,{position:"relative",className:"demo-container",h:4
541300,overflow:"hidden",children:[e.jsx(R,{items:["https://picsum.photos/id/287/300/300","https://picsum.photos/id/1001/300/300","https://picsum.photos/id/1025/300/300","https://picsum.photos/id/1026/300/300","https://picsum.photos/id/1027/300/300","https://picsum.photos/id/1028/300/300","https://picsum.photos/id/1029/300/300","https://picsum.photos/id/1030/300/300"],variant:n},m),e.jsxs(s,{position:"absolute",justifyContent:"center",flexDirection:"column",alignItems:"center",children:[e.jsx(o,{className:"demo-instruction",fontSize:"clamp(2rem, 6vw, 6rem)",fontWeight:600,mb:0,children:"Hover Me."}),e.jsxs(o,{fontSize:"18px",fontWeight:600,color:"#a6a6a6",mt:0,children:["Variant ",n]})]})]}),e.jsx(s,{justify:"flex-end",mt:2,mb:-2,children:e.jsx(I,{backgroundId:"image-trail",currentProps:{variant:n},defaultProps:t})}),e.jsx(T,{children:e.jsx(b,{title:"Variant",options:[{value:"1",label:"1"},{value:"2",label:"2"},{value:"3",label:"3"},{value:"4",label:"4"},{value:"5",label:"5"},{value:"6",label:"6"},{value:"7",label:"7"},{value:"8",label:"8"}],value:n,onChange:d=>{a("variant",d),c()}})}),e.jsx(x,{data:l}),e.jsx(E,{dependencyList:["gsap"]})]}),e.jsx(y,{children:e.jsx(f,{codeObject:z,componentName:"ImageTrail"})})]})})};export{J as default};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.