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); | |
} | |
}); | |
} | |
} | |