Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
| export namespace VanillaTilt { | |
| /** | |
| * Options which configures the tilting | |
| */ | |
| export interface TiltOptions { | |
| /** | |
| * Reverse the tilt direction | |
| */ | |
| reverse?: boolean; | |
| /** | |
| * Max tilt rotation (degrees) | |
| */ | |
| max?: number; | |
| /** | |
| * Transform perspective, the lower the more extreme the tilt gets. | |
| */ | |
| perspective?: number; | |
| /** | |
| * 2 = 200%, 1.5 = 150%, etc.. | |
| */ | |
| scale?: number; | |
| /** | |
| * Speed of the enter/exit transition | |
| */ | |
| speed?: number; | |
| /** | |
| * Set a transition on enter/exit. | |
| */ | |
| transition?: boolean; | |
| /** | |
| * What axis should be disabled. Can be X or Y. | |
| */ | |
| axis?: null | "x" | "y"; | |
| /** | |
| * If the tilt effect has to be reset on exit. | |
| */ | |
| reset?: boolean; | |
| /** | |
| * Easing on enter/exit. | |
| */ | |
| easing?: string; | |
| /** | |
| * Added (@julien-c) | |
| */ | |
| glare?: boolean; | |
| 'max-glare'?: number; | |
| } | |
| export interface TiltValues { | |
| /** | |
| * The current tilt on the X axis | |
| */ | |
| tiltX: number; | |
| /** | |
| * The current tilt on the Y axis | |
| */ | |
| tiltY: number; | |
| /** | |
| * The current percentage on the X axis | |
| */ | |
| percentageX: number; | |
| /** | |
| * The current percentage on the Y axis | |
| */ | |
| percentageY: number; | |
| } | |
| export interface HTMLVanillaTiltElement extends HTMLElement { | |
| vanillaTilt: VanillaTilt | |
| } | |
| } | |
| export class VanillaTilt { | |
| width: number | null; | |
| height: number | null; | |
| left: number | null; | |
| top: number | null; | |
| element: VanillaTilt.HTMLVanillaTiltElement; | |
| settings: VanillaTilt.TiltOptions; | |
| reverse : -1 | 1; | |
| glare: boolean; | |
| glarePrerender: boolean; | |
| transitionTimeout: number | null; | |
| updateCall: number | null; | |
| glareElementWrapper: HTMLElement; | |
| glareElement: HTMLElement; | |
| updateBind: () => void; | |
| resetBind: () => void; | |
| onMouseEnterBind: (e: Event) => void; | |
| onMouseMoveBind: (e: Event) => void; | |
| onMouseLeaveBind: (e: Event) => void; | |
| event: MouseEvent; | |
| constructor(element, settings: VanillaTilt.TiltOptions = {}) { | |
| if (!(element instanceof Node)) { | |
| throw ("Can't initialize VanillaTilt because " + element + " is not a Node."); | |
| } | |
| this.width = null; | |
| this.height = null; | |
| this.left = null; | |
| this.top = null; | |
| this.transitionTimeout = null; | |
| this.updateCall = null; | |
| this.updateBind = this.update.bind(this); | |
| this.resetBind = this.reset.bind(this); | |
| this.element = element as VanillaTilt.HTMLVanillaTiltElement; | |
| this.settings = this.extendSettings(settings); | |
| this.reverse = this.settings.reverse ? -1 : 1; | |
| this.glare = this.isSettingTrue(this.settings.glare); | |
| this.glarePrerender = this.isSettingTrue(this.settings["glare-prerender"]); | |
| if (this.glare) { | |
| this.prepareGlare(); | |
| } | |
| this.addEventListeners(); | |
| } | |
| isSettingTrue(setting) { | |
| return setting === "" || setting === true || setting === 1; | |
| } | |
| addEventListeners() { | |
| this.onMouseEnterBind = this.onMouseEnter.bind(this); | |
| this.onMouseMoveBind = this.onMouseMove.bind(this); | |
| this.onMouseLeaveBind = this.onMouseLeave.bind(this); | |
| this.onWindowResizeBind = this.onWindowResizeBind.bind(this); | |
| this.element.addEventListener("mouseenter", this.onMouseEnterBind); | |
| this.element.addEventListener("mousemove", this.onMouseMoveBind); | |
| this.element.addEventListener("mouseleave", this.onMouseLeaveBind); | |
| if (this.glare) { | |
| window.addEventListener("resize", this.onWindowResizeBind); | |
| } | |
| } | |
| onMouseEnter(event) { | |
| this.updateElementPosition(); | |
| (<any>this.element.style).willChange = "transform"; | |
| this.setTransition(); | |
| } | |
| onMouseMove(event) { | |
| if (this.updateCall !== null) { | |
| cancelAnimationFrame(this.updateCall); | |
| } | |
| this.event = event; | |
| this.updateCall = requestAnimationFrame(this.updateBind); | |
| } | |
| onMouseLeave(event) { | |
| this.setTransition(); | |
| if (this.settings.reset) { | |
| requestAnimationFrame(this.resetBind); | |
| } | |
| } | |
| reset() { | |
| this.event = { | |
| pageX: this.left! + this.width! / 2, | |
| pageY: this.top! + this.height! / 2 | |
| } as MouseEvent; | |
| this.element.style.transform = "perspective(" + this.settings.perspective + "px) " + | |
| "rotateX(0deg) " + | |
| "rotateY(0deg) " + | |
| "scale3d(1, 1, 1)" | |
| ; | |
| if (this.glare) { | |
| this.glareElement.style.transform = 'rotate(180deg) translate(-50%, -50%)'; | |
| this.glareElement.style.opacity = '0'; | |
| } | |
| } | |
| getValues() { | |
| let x = (this.event.clientX - this.left!) / this.width!; | |
| let y = (this.event.clientY - this.top!) / this.height!; | |
| x = Math.min(Math.max(x, 0), 1); | |
| y = Math.min(Math.max(y, 0), 1); | |
| let tiltX = (this.reverse * (this.settings.max! / 2 - x * this.settings.max!)).toFixed(2); | |
| let tiltY = (this.reverse * (y * this.settings.max! - this.settings.max! / 2)).toFixed(2); | |
| let angle = Math.atan2(this.event.clientX - (this.left! + this.width! / 2), -(this.event.clientY - (this.top! + this.height! / 2))) * (180 / Math.PI); | |
| return { | |
| tiltX: tiltX, | |
| tiltY: tiltY, | |
| percentageX: x * 100, | |
| percentageY: y * 100, | |
| angle: angle | |
| }; | |
| } | |
| updateElementPosition() { | |
| let rect = this.element.getBoundingClientRect(); | |
| this.width = this.element.offsetWidth; | |
| this.height = this.element.offsetHeight; | |
| this.left = rect.left; | |
| this.top = rect.top; | |
| } | |
| update() { | |
| const values = this.getValues(); | |
| this.element.style.transform = [ | |
| "perspective(" + this.settings.perspective + "px) ", | |
| "rotateX(" + (this.settings.axis === "x" ? 0 : values.tiltY) + "deg) ", | |
| "rotateY(" + (this.settings.axis === "y" ? 0 : values.tiltX) + "deg) ", | |
| "scale3d(" + this.settings.scale + ", " + this.settings.scale + ", " + this.settings.scale + ")", | |
| ].join(" "); | |
| if (this.glare) { | |
| this.glareElement.style.transform = `rotate(${values.angle}deg) translate(-50%, -50%)`; | |
| this.glareElement.style.opacity = `${values.percentageY * this.settings["max-glare"]! / 100}`; | |
| } | |
| this.element.dispatchEvent(new CustomEvent("tiltChange", { | |
| "detail": values | |
| })); | |
| this.updateCall = null; | |
| } | |
| /** | |
| * Appends the glare element (if glarePrerender equals false) | |
| * and sets the default style | |
| */ | |
| prepareGlare() { | |
| // If option pre-render is enabled we assume all html/css is present for an optimal glare effect. | |
| if (!this.glarePrerender) { | |
| // Create glare element | |
| const jsTiltGlare = document.createElement("div"); | |
| jsTiltGlare.classList.add("js-tilt-glare"); | |
| const jsTiltGlareInner = document.createElement("div"); | |
| jsTiltGlareInner.classList.add("js-tilt-glare-inner"); | |
| jsTiltGlare.appendChild(jsTiltGlareInner); | |
| this.element.appendChild(jsTiltGlare); | |
| } | |
| this.glareElementWrapper = this.element.querySelector(".js-tilt-glare") as HTMLElement; | |
| this.glareElement = this.element.querySelector(".js-tilt-glare-inner") as HTMLElement; | |
| if (this.glarePrerender) { | |
| return ; | |
| } | |
| Object.assign(this.glareElementWrapper.style, { | |
| "position": "absolute", | |
| "top": "0", | |
| "left": "0", | |
| "width": "100%", | |
| "height": "100%", | |
| "overflow": "hidden", | |
| 'pointer-events': 'none', | |
| }); | |
| Object.assign(this.glareElement.style, { | |
| 'position': 'absolute', | |
| 'top': '50%', | |
| 'left': '50%', | |
| 'pointer-events': 'none', | |
| 'background-image': `linear-gradient(0deg, rgba(255,255,255,0) 0%, rgba(255,255,255,1) 100%)`, | |
| 'width': `${this.element.offsetWidth * 2}px`, | |
| 'height': `${this.element.offsetWidth * 2}px`, | |
| 'transform': 'rotate(180deg) translate(-50%, -50%)', | |
| 'transform-origin': '0% 0%', | |
| 'opacity': '0', | |
| }); | |
| } | |
| updateGlareSize() { | |
| Object.assign(this.glareElement.style, { | |
| 'width': `${this.element.offsetWidth * 2}`, | |
| 'height': `${this.element.offsetWidth * 2}`, | |
| }); | |
| } | |
| onWindowResizeBind() { | |
| this.updateGlareSize(); | |
| } | |
| setTransition() { | |
| if (this.transitionTimeout) { | |
| clearTimeout(this.transitionTimeout); | |
| } | |
| // this.element.style.transition = `${this.settings.speed}ms ${this.settings.easing}`; | |
| /// From openai: | |
| this.element.style.transition = `transform .4s cubic-bezier(0,0,.2,1)`; | |
| if (this.glare) { | |
| this.glareElement.style.transition = `opacity ${this.settings.speed}ms ${this.settings.easing}`; | |
| } | |
| this.transitionTimeout = setTimeout(() => { | |
| this.element.style.transition = ""; | |
| if (this.glare) { | |
| this.glareElement.style.transition = ""; | |
| } | |
| }, this.settings.speed); | |
| } | |
| extendSettings(settings) { | |
| let defaultSettings = { | |
| reverse: false, | |
| max: 35, | |
| perspective: 1000, | |
| easing: "cubic-bezier(.03,.98,.52,.99)", | |
| scale: "1", | |
| speed: "300", | |
| transition: true, | |
| axis: null, | |
| glare: false, | |
| "max-glare": 1, | |
| "glare-prerender": false, | |
| reset: true, | |
| }; | |
| let newSettings = {}; | |
| for (var property in defaultSettings) { | |
| if (property in settings) { | |
| newSettings[property] = settings[property]; | |
| } else if (this.element.hasAttribute("data-tilt-" + property)) { | |
| let attribute = this.element.getAttribute("data-tilt-" + property); | |
| try { | |
| newSettings[property] = JSON.parse(<any>attribute); | |
| } catch (e) { | |
| newSettings[property] = attribute; | |
| } | |
| } else { | |
| newSettings[property] = defaultSettings[property]; | |
| } | |
| } | |
| return newSettings; | |
| } | |
| static init(elements, settings: VanillaTilt.TiltOptions = {}) { | |
| if (elements instanceof Node) { | |
| elements = [elements]; | |
| } | |
| if (elements instanceof NodeList) { | |
| elements = [].slice.call(elements); | |
| } | |
| if (!(elements instanceof Array)) { | |
| return ; | |
| } | |
| elements.forEach((element) => { | |
| if (!("vanillaTilt" in element)) { | |
| element.vanillaTilt = new VanillaTilt(element, settings); | |
| } | |
| }); | |
| } | |
| } | |