UNPKG

vevet

Version:

Vevet is a JavaScript library for creative development that simplifies crafting rich interactions like split text animations, carousels, marquees, preloading, and more.

163 lines (132 loc) 4.07 kB
import { IViewport, TViewportCallbacks } from './types'; import { Callbacks } from '@/base/Callbacks'; import { ICoreProps } from '@/core/types'; import { addEventListener } from '@/utils/listeners'; interface IProps { prefix: string; props: ICoreProps; isMobile: boolean; } export function createViewport({ prefix, props, isMobile }: IProps) { const html = document.documentElement; let styles = document.getElementById('vevet_css_preset'); if (!styles) { styles = document.createElement('style'); styles.id = 'vevet_css_preset'; document.body.appendChild(styles); } const mqDesktop = window.matchMedia(`(min-width: ${props.md + 0.001}px)`); const mqTablet = window.matchMedia( `(min-width: ${props.sm + 0.001}px) and (max-width: ${props.md}px)`, ); const mqPhone = window.matchMedia(`(max-width: ${props.sm}px)`); // create callbacks const callbacks: TViewportCallbacks = new Callbacks(); // default data const data: IViewport = { width: 0, height: 0, sHeight: 0, vw: 0, vh: 0, svh: 0, rem: 16, landscape: false, portrait: false, dpr: window.devicePixelRatio, lowerDpr: window.devicePixelRatio, sm: true, md: false, lg: false, }; // update values for the first time updateValues(); updateClassNames(); updateCSSVars(); // add resize events let debounce: NodeJS.Timeout | undefined; addEventListener(window, 'resize', () => { if (debounce) { clearTimeout(debounce); debounce = undefined; } debounce = setTimeout(() => onResize(), props.resizeDebounce); }); /** Event on window resize */ function onResize() { const { width: prevWidth, height: prevHeight } = data; updateValues(); updateClassNames(); updateCSSVars(); const { width, height } = data; callbacks.emit('any', undefined); if (width !== prevWidth && height === prevHeight) { callbacks.emit('width_', undefined); } if (height !== prevHeight && width === prevWidth) { callbacks.emit('height_', undefined); } if (width !== prevWidth && height !== prevHeight) { callbacks.emit('both', undefined); } if (width !== prevWidth) { callbacks.emit('width', undefined); } if (height !== prevHeight) { callbacks.emit('height', undefined); } } /** Update viewport values */ function updateValues() { const { width: prevWidth } = data; const root = document.documentElement; const rootStyles = getComputedStyle(root); data.width = window.innerWidth; data.height = window.innerHeight; data.vw = data.width / 100; data.vh = data.height / 100; data.rem = parseFloat(rootStyles.fontSize); data.landscape = data.width > data.height; data.portrait = data.width < data.height; data.dpr = window.devicePixelRatio; data.lowerDpr = !isMobile ? 1 : Math.min(data.dpr, 2); data.sm = false; data.md = false; data.lg = false; if (mqPhone.matches) { data.sm = true; } else if (mqTablet.matches) { data.md = true; } else if (mqDesktop.matches) { data.lg = true; } // update sHeight && svh only when the width changes // or for desktop if (prevWidth !== data.width || !data.sHeight || !isMobile) { data.sHeight = root.clientHeight; data.svh = data.sHeight / 100; } } /** Update page classnames according to the viewport data */ function updateClassNames() { if (!props.applyClassNames) { return; } html.classList.toggle(`${prefix}lg`, data.lg); html.classList.toggle(`${prefix}md`, data.md); html.classList.toggle(`${prefix}sm`, data.sm); html.classList.toggle(`${prefix}landscape`, data.landscape); html.classList.toggle(`${prefix}portrait`, data.portrait); } /** Update CSS variables */ function updateCSSVars() { styles!.innerHTML = ` html { --vw: ${data.vw}px; --vh: ${data.vh}px; --svh: ${data.svh}px; } `; } return { data, callbacks }; }