vevet
Version:
Vevet is a JavaScript library for creative development that simplifies crafting rich interactions like split text animations, carousels, marquees, preloading, and more.
193 lines (157 loc) • 4.8 kB
text/typescript
import { Callbacks } from '@/base/Callbacks';
import { ICoreProps } from '@/core/types';
import { cnToggle } from '@/internal/cn';
import { body, doc, html } from '@/internal/env';
import { isFiniteNumber } from '@/internal/isFiniteNumber';
import { addEventListener } from '@/utils/listeners';
import { IViewport, TViewportCallbacks } from './types';
interface IProps {
prefix: string;
props: ICoreProps;
isMobile: boolean;
isInApp: boolean;
browserName: string;
}
export function createViewport({
prefix,
props,
isMobile,
isInApp,
browserName,
}: IProps) {
// create styles
let styles = doc.getElementById('vevet_css_preset');
if (!styles) {
styles = doc.createElement('style');
styles.id = 'vevet_css_preset';
body.appendChild(styles);
}
// create svh helper
const svhHelper = doc.createElement('div');
const { style } = svhHelper;
svhHelper.id = 'vevet_svh_helper';
style.position = 'fixed';
style.top = '-100svh';
style.left = '-100px';
style.width = '1px';
style.height = '100svh';
body.appendChild(svhHelper);
// create callbacks
const callbacks: TViewportCallbacks = new Callbacks();
// default data
const data: IViewport = {
width: 0,
height: 0,
sHeight: 0,
vw: 0,
vh: 0,
svh: 0,
scrollbarWidth: 0,
rem: 16,
landscape: false,
portrait: false,
dpr: window.devicePixelRatio,
lowerDpr: window.devicePixelRatio,
};
// update values for the first time
updateValues();
updateClassNames();
updateCSSVars();
// add resize events
let debounce: NodeJS.Timeout | undefined;
function debounceResize() {
if (debounce) {
clearTimeout(debounce);
debounce = undefined;
}
if (props.resizeDebounce) {
debounce = setTimeout(() => onResize(), props.resizeDebounce);
} else {
onResize();
}
}
addEventListener(window, 'resize', () => debounceResize());
const observer = new ResizeObserver(() => debounceResize());
observer.observe(html);
observer.observe(body);
/** Event on window resize */
function onResize() {
const { width: prevWidth, height: prevHeight } = data;
updateValues();
updateClassNames();
updateCSSVars();
const { width, height } = data;
callbacks.emit('trigger', undefined);
if (width !== prevWidth || height !== prevHeight) {
callbacks.emit('any', undefined);
}
if (width !== prevWidth && height === prevHeight) {
callbacks.emit('onlyWidth', undefined);
}
if (height !== prevHeight && width === prevWidth) {
callbacks.emit('onlyHeight', 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 vWidth = window.innerWidth;
const vHeight = window.innerHeight;
data.width = vWidth;
data.height = vHeight;
data.scrollbarWidth = vWidth - html.clientWidth;
data.vw = data.width / 100;
data.vh = data.height / 100;
const rootStyles = getComputedStyle(html);
const fontSize = parseFloat(rootStyles.fontSize);
data.rem = isFiniteNumber(fontSize) ? fontSize : 16;
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);
// for in-app browser, update svh only if width changed
if (isMobile && (isInApp || browserName.includes('fxios'))) {
const rootHeight = html.clientHeight;
if (prevWidth !== data.width || !data.sHeight) {
data.sHeight = rootHeight;
data.svh = data.sHeight / 100;
} else if (prevWidth === data.width && rootHeight < data.sHeight) {
data.sHeight = rootHeight;
data.svh = data.sHeight / 100;
}
} else {
// when other browser, update svh directly
data.svh = svhHelper.clientHeight / 100 || html.clientHeight / 100;
data.sHeight = data.svh * 100;
}
}
/** Update page classnames according to the viewport data */
function updateClassNames() {
if (!props.applyClassNames) {
return;
}
cnToggle(html, `${prefix}landscape`, data.landscape);
cnToggle(html, `${prefix}portrait`, data.portrait);
}
/** Update CSS variables */
function updateCSSVars() {
styles!.innerHTML = `
html {
--vw: ${data.vw}px;
--vh: ${data.vh}px;
--svh: ${data.svh}px;
--scrollbar-width: ${data.scrollbarWidth}px;
}
`;
}
return { data, callbacks };
}