Advertisement
Guest User

Untitled

a guest
Jul 23rd, 2021
119
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /* tslint:disable:variable-name */
  2. import {Inject, Injectable, PLATFORM_ID} from '@angular/core';
  3. import {isPlatformBrowser} from '@angular/common';
  4.  
  5. @Injectable({
  6.   providedIn: 'root'
  7. })
  8. export class PerspectiveCardService {
  9.  
  10.   // --------------------------------------------------------------------
  11.   // VARIABLES DECLARATIONS
  12.   // --------------------------------------------------------------------
  13.  
  14.   private EPSILON = 0.001;
  15.   private _element: HTMLElement | null = null;
  16.   private _debug: any;
  17.   private _zoomSize: any;
  18.   private _intensity: any;
  19.   private _ambient: any;
  20.   private transformer: any;
  21.   private shine: any;
  22.   private _playing: any;
  23.   private observer: IntersectionObserver;
  24.   private _lastFrameTime: any;
  25.   private _lastDelta: any;
  26.   private _delta: any;
  27.   private _motionOff: boolean;
  28.   private _pointerControlled: any;
  29.   private _tPoint = [0, 0, -800];
  30.   private _zoom: any;
  31.   private _center: any;
  32.   private _lookDifferential: any;
  33.   private _lookPoint = [0, 0, -800];
  34.   private _axis: any;
  35.   private _position = [0, 0];
  36.   private _size: any;
  37.   private debounceTimer: any;
  38.   private isBrowser: boolean;
  39.  
  40.   // --------------------------------------------------------------------
  41.   // CONSTRUCTOR
  42.   // --------------------------------------------------------------------
  43.  
  44.   constructor(element, settings: any = {}, isBrowser) {
  45.     this.isBrowser = isBrowser;
  46.  
  47.     if (this.isBrowser) {
  48.  
  49.       // Set the element
  50.       this.element = element;
  51.  
  52.       // set settings
  53.       this.debug =
  54.         settings.debug || this.element.hasAttribute('data-debug') || false;
  55.       this.zoomSize =
  56.         settings.zoom || +this.element.getAttribute('data-zoom') || 40;
  57.       this.intensity =
  58.         settings.intensity ||
  59.         this.element.getAttribute('data-intensity') ||
  60.         10;
  61.  
  62.       this.ambient = -1;
  63.  
  64.       if (settings.ambient !== undefined && settings.ambient !== false) {
  65.         const settingsVal = settings.ambient;
  66.         if (settingsVal === true) {
  67.           this.ambient = 0;
  68.         } else {
  69.           this.ambient = settingsVal;
  70.         }
  71.       } else if (this.element.hasAttribute('data-ambient')) {
  72.         const dataVal = this.element.getAttribute('data-ambient');
  73.  
  74.         if (dataVal !== 'false') {
  75.           if (dataVal === '' || dataVal === 'true') {
  76.             this.ambient = 0;
  77.           } else {
  78.             this.ambient = +dataVal;
  79.           }
  80.         }
  81.       }
  82.  
  83.       // Find the transformer and shine elements. We save these so we
  84.       // don't waste proc time doing it every frame
  85.       this.transformer = this.element.querySelector(
  86.         '.perspective-card__transformer'
  87.       );
  88.       this.shine = this.element.querySelector('.perspective-card__shine');
  89.  
  90.  
  91.       // Add event listeners for resize, scroll, pointer enter and leave
  92.       window.addEventListener('resize', this.resize.bind(this));
  93.       window.addEventListener('scroll', this.resize.bind(this));
  94.       this.element.addEventListener('pointerenter', this.pointerEnter.bind(this));
  95.       this.element.addEventListener('pointerleave', this.pointerLeave.bind(this));
  96.  
  97.  
  98.       if (this.ambient >= 0) {
  99.         // Set up and bind the intersection observer
  100.         this.observer = new IntersectionObserver(this.intersect, {
  101.           rootMargin: '0%',
  102.           threshold: [0.1]
  103.         });
  104.         this.observer.observe(this.element);
  105.       }
  106.  
  107.       // Initial resize to find the location and dimensions of the element
  108.       this.resize();
  109.     }
  110.  
  111.   }
  112.  
  113.  
  114.   // --------------------------------------------------------------------
  115.   // GETTER AND SETTER
  116.   // --------------------------------------------------------------------
  117.  
  118.   set motionOff(value: boolean) {
  119.     this._motionOff = value;
  120.   }
  121.  
  122.   get motionOff(): boolean {
  123.     return this._motionOff === true;
  124.   }
  125.  
  126.   set element(value: HTMLElement) {
  127.     if (value instanceof HTMLElement) {
  128.       this._element = value;
  129.     }
  130.   }
  131.  
  132.   get element(): HTMLElement | null {
  133.     return this._element;
  134.   }
  135.  
  136.   set position(value: number[]) {
  137.     if (value instanceof Array && value.length >= 2) {
  138.       this._position = value;
  139.     }
  140.   }
  141.  
  142.   get position(): number[] {
  143.     return this._position;
  144.   }
  145.  
  146.   set tPoint(value: number[]) {
  147.     if (value instanceof Array && value.length >= 3) {
  148.       this._tPoint = value;
  149.       this.calculateLookDifferential();
  150.     }
  151.   }
  152.  
  153.   get tPoint(): number[] {
  154.     return this._tPoint;
  155.   }
  156.  
  157.   set lookPoint(value: number[]) {
  158.     if (value instanceof Array && value.length >= 3) {
  159.       this.calculateLookDifferential();
  160.       this._lookPoint = value;
  161.     }
  162.   }
  163.  
  164.   get lookPoint(): number[] {
  165.     return this._lookPoint;
  166.   }
  167.  
  168.   set center(value: number[]) {
  169.     if (value instanceof Array && value.length >= 3) {
  170.       this._center = value;
  171.     }
  172.   }
  173.  
  174.   get center(): number[] {
  175.     return this._center || [0, 0, 0];
  176.   }
  177.  
  178.   set zoom(value: any) {
  179.     // @ts-ignore
  180.     if (!isNaN(value)) {
  181.       this._zoom = value;
  182.     }
  183.   }
  184.  
  185.   get zoom(): any {
  186.     return this._zoom || 0;
  187.   }
  188.  
  189.   set zoomSize(value: number) {
  190.     if (!isNaN(value)) {
  191.       this._zoomSize = value;
  192.     }
  193.   }
  194.  
  195.   get zoomSize(): number {
  196.     return this._zoomSize || 40;
  197.   }
  198.  
  199.   set intensity(value: number) {
  200.     if (!isNaN(value)) {
  201.       this._intensity = value;
  202.     }
  203.   }
  204.  
  205.   get intensity(): number {
  206.     return this._intensity || 10;
  207.   }
  208.  
  209.   set size(value: number[]) {
  210.     if (value instanceof Array && value.length >= 2) {
  211.       this._size = value;
  212.     }
  213.   }
  214.  
  215.   get size(): number[] {
  216.     return this._size || [0, 0];
  217.   }
  218.  
  219.   set debug(value: boolean) {
  220.     this._debug = value;
  221.   }
  222.  
  223.   get debug(): boolean {
  224.     return this._debug || false;
  225.   }
  226.  
  227.   set ambient(value: any) {
  228.     this._ambient = value;
  229.   }
  230.  
  231.   get ambient(): any {
  232.     return this._ambient || false;
  233.   }
  234.  
  235.   set axis(value: number[]) {
  236.     if (value instanceof Array && value.length >= 2) {
  237.       this._axis = value;
  238.     }
  239.   }
  240.  
  241.   get axis(): number[] {
  242.     return this._axis || [0, 0];
  243.   }
  244.  
  245.   set playing(value: boolean) {
  246.     if (!this.playing && value === true) {
  247.       // Reset last frame time
  248.       this.lastFrameTime = 0;
  249.  
  250.  
  251.       requestAnimationFrame(this.play.bind(this));
  252.     }
  253.     this._playing = value === true;
  254.   }
  255.  
  256.   get playing(): boolean {
  257.     return this._playing === true;
  258.   }
  259.  
  260.   set lastFrameTime(value: number) {
  261.     if (+value) {
  262.       this._lastFrameTime = value;
  263.     }
  264.   }
  265.  
  266.   get lastFrameTime(): number {
  267.     return this._lastFrameTime || 0;
  268.   }
  269.  
  270.   set delta(value: number) {
  271.     if (+value) {
  272.       this._delta = value;
  273.     }
  274.   }
  275.  
  276.   get delta(): number {
  277.     return this._delta || 0;
  278.   }
  279.  
  280.   set lastDelta(value: number) {
  281.     if (+value) {
  282.       this._lastDelta = value;
  283.     }
  284.   }
  285.  
  286.   get lastDelta(): number {
  287.     return this._lastDelta || 0;
  288.   }
  289.  
  290.   set pointerControlled(value: boolean) {
  291.     if (!this.pointerControlled && value === true) {
  292.       window.addEventListener('pointermove', this.pointerMove.bind(this));
  293.     } else if (this.pointerControlled && value === false) {
  294.       window.removeEventListener('pointermove', this.pointerMove.bind(this));
  295.     }
  296.     this._pointerControlled = value;
  297.   }
  298.  
  299.   get pointerControlled(): boolean {
  300.     return this._pointerControlled === true;
  301.   }
  302.  
  303.   // --------------------------------------------------------------------
  304.   // FUNCTIONS
  305.   // --------------------------------------------------------------------
  306.  
  307.  
  308.   public play(delta, raf = true): void {
  309.  
  310.     // If `playing` is true, then request the animation frame again
  311.     if (this.playing && raf === true) {
  312.       requestAnimationFrame(this.play.bind(this));
  313.     }
  314.  
  315.     // Set the last frame time in order to derive the sensible delta
  316.     this.lastFrameTime = Math.max(16, Math.min(32, delta - this.lastDelta));
  317.     this.lastDelta = delta;
  318.     this.delta += this.lastFrameTime;
  319.  
  320.     if (this.motionOff) {
  321.       return;
  322.     }
  323.  
  324.     // Set the divisor for animations based on the last frame time
  325.     const divisor = 1 / this.lastFrameTime;
  326.     // if (isNaN(divisor) || divisor === Infinity) divisor = 1;
  327.  
  328.     // If this element is not pointer controlled then we want to animate
  329.     // the ambient target point value around somehow. Here we use a simple
  330.     // fourier simulation.
  331.     if (!this.pointerControlled) {
  332.       // const d = delta * 0.0005;
  333.       // const a = 1.8 + Math.sin(2. * d + .2) + .4 * Math.cos(4. * 2. * d);
  334.       // const l = a * 80.;
  335.  
  336.       const d = this.delta * 0.0001 + this.ambient;
  337.       const s = Math.sin(d * 2);
  338.       const c = Math.cos(d * 0.5);
  339.       const l = this.intensity * 10 * Math.cos(d * 3.542 + 1234.5);
  340.       // Some really arbitrary numbers here. They don't mean anythign in particular, they just work.
  341.  
  342.       this.tPoint = [c * l, s * l, this.tPoint[2]];
  343.     }
  344.  
  345.     // If our zoom differential (the different between the zoom and
  346.     // target zoom) is greater than the EPS value. We should animate it
  347.     if (Math.abs(this.zoom - this.center[2]) > this.EPSILON) {
  348.       this.center = [
  349.         this.center[0],
  350.         this.center[1],
  351.         this.center[2] + (this.zoom - this.center[2]) * (divisor * 2)
  352.       ];
  353.     }
  354.  
  355.     // If our look differential (the difference between the look
  356.     // point and the target point) is greater than 2 then we should
  357.     // animate it. We use a relatively arbitrary value of 2 here
  358.     // because we're using the square of the distance (to save
  359.     // unecessary calculation) here.
  360.     if (this._lookDifferential > 2) {
  361.       this.lookPoint = [
  362.         this.lookPoint[0] +
  363.         (this.tPoint[0] - this.lookPoint[0]) * (divisor * 2),
  364.         this.lookPoint[1] +
  365.         (this.tPoint[1] - this.lookPoint[1]) * (divisor * 2),
  366.         this.lookPoint[2] + (this.tPoint[2] - this.lookPoint[2]) * (divisor * 2)
  367.       ];
  368.     }
  369.  
  370.     // Find the wold matrix using the targetTo method (see above)
  371.     const worldMatrix = PerspectiveCardService.targetTo(this.center, this.lookPoint, [
  372.       0,
  373.       1,
  374.       0
  375.     ]);
  376.  
  377.     // Find the polar coordinates for the rendition of the gradient.
  378.     const angle =
  379.       Math.atan2(this.lookPoint[1], this.lookPoint[0]) + Math.PI * 0.5;
  380.     const len = Math.hypot(this.lookPoint[0], this.lookPoint[1]);
  381.  
  382.     // Transform the transformer element using the calculated values
  383.     const matrix = `matrix3d(${worldMatrix[0]},${worldMatrix[1]},${worldMatrix[2]},${worldMatrix[3]},${worldMatrix[4]},${worldMatrix[5]},${worldMatrix[6]},${worldMatrix[7]},${worldMatrix[8]},${worldMatrix[9]},${worldMatrix[10]},${worldMatrix[11]},${worldMatrix[12]},${worldMatrix[13]},${worldMatrix[14]},${worldMatrix[15]})`;
  384.     this.transformer.style.transform = matrix;
  385.  
  386.     // Draw the gradient using the polar coordinates.
  387.     this.shine.style.background = `linear-gradient(${angle}rad, rgba(255,255,255,${Math.max(
  388.       0.01,
  389.       Math.abs(len * 0.002)
  390.     )}) 0%, rgba(255,255,255,${Math.max(
  391.       0.01,
  392.       Math.abs(len * 0.002)
  393.     )}) 5%, rgba(255,255,255,0) 80%)`;
  394.   }
  395.  
  396.   public calculateLookDifferential(): void {
  397.     const d = [
  398.       this.lookPoint[0] - this.tPoint[0],
  399.       this.lookPoint[1] - this.tPoint[1],
  400.       this.lookPoint[2] - this.tPoint[2]
  401.     ];
  402.     this._lookDifferential = d[0] * d[0] + d[1] * d[1] + d[2] * d[2];
  403.   }
  404.  
  405.   public pointerMove(e): void {
  406.     this.tPoint = [
  407.       e.clientX - this.axis[0],
  408.       e.clientY - this.axis[1],
  409.       this.tPoint[2]
  410.     ];
  411.   }
  412.  
  413.   public pointerEnter(): void {
  414.     this.pointerControlled = true;
  415.     this.zoom = this.zoomSize;
  416.     this.element.classList.add('perspective-card--over');
  417.  
  418.     if (this.ambient === false) {
  419.       this.playing = true;
  420.     }
  421.   }
  422.  
  423.   public pointerLeave(): void {
  424.     this.pointerControlled = false;
  425.     this.zoom = 0;
  426.     this.element.classList.remove('perspective-card--over');
  427.  
  428.     if (this.ambient < 0) {
  429.       this.playing = false;
  430.       setTimeout(() => {
  431.         this.transformer.style.transform = `matrix3d(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1)`;
  432.         this.shine.style.background = `none`;
  433.       }, 100);
  434.     }
  435.   }
  436.  
  437.   public resize(): void {
  438.     const resize = () => {
  439.       const pos = this.element.getBoundingClientRect();
  440.       this.position = [pos.left, pos.top];
  441.       this.size = [pos.width, pos.height];
  442.       this.axis = [
  443.         this.position[0] + this.size[0] * 0.5,
  444.         this.position[1] + this.size[1] * 0.5
  445.       ];
  446.     };
  447.     clearTimeout(this.debounceTimer);
  448.     this.debounceTimer = setTimeout(resize, 300);
  449.   }
  450.  
  451.   public intersect(entries, observer): void {
  452.     // Loop through the entries and set up the playing state based on whether the element is onscreen or not.
  453.     entries.forEach((entry, i) => {
  454.       if (entry.isIntersecting) {
  455.         this.playing = true;
  456.       } else {
  457.         this.playing = false;
  458.       }
  459.     });
  460.   }
  461.  
  462.   // tslint:disable-next-line:member-ordering
  463.   static targetTo(eye, target, up): any {
  464.     if (eye.array) {
  465.       eye = eye.array;
  466.     }
  467.     if (target.array) {
  468.       target = target.array;
  469.     }
  470.     if (up.array) {
  471.       up = up.array;
  472.     }
  473.  
  474.     if (eye.length && eye.length >= 3 && target.length && target.length >= 3 && up.length && up.length >= 3) {
  475.       const e = {x: eye[0], y: eye[1], z: eye[2]};
  476.       const c = {x: target[0], y: target[1], z: target[2]};
  477.       const u = {x: up[0], y: up[1], z: up[2]};
  478.  
  479.       const off = {x: e.x - c.x, y: e.y - c.y, z: e.z - c.z};
  480.       let l = off.x * off.x + off.y * off.y + off.z * off.z;
  481.       if (l > 0) {
  482.         l = 1 / Math.sqrt(l);
  483.         off.x *= l;
  484.         off.y *= l;
  485.         off.z *= l;
  486.       }
  487.  
  488.       const or = {x: u.y * off.z - u.z * off.y, y: u.z * off.x - u.x * off.z, z: u.x * off.y - u.y * off.x};
  489.       l = or.x * or.x + or.y * or.y + or.z * or.z;
  490.       if (l > 0) {
  491.         l = 1 / Math.sqrt(l);
  492.         or.x *= l;
  493.         or.y *= l;
  494.         or.z *= l;
  495.       }
  496.  
  497.       return [
  498.         or.x,
  499.         or.y,
  500.         or.z,
  501.         0,
  502.         off.y * or.z - off.z * or.y,
  503.         off.z * or.x - off.x * or.z,
  504.         off.x * or.y - off.y * or.x,
  505.         0,
  506.         off.x,
  507.         off.y,
  508.         off.z,
  509.         0,
  510.         e.x,
  511.         e.y,
  512.         e.z,
  513.         1
  514.       ];
  515.     }
  516.   }
  517. }
  518.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement