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
text/typescript
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 };
}