diff --git a/src/hooks/useHasFocus/useHasFocus.test.tsx b/src/hooks/useHasFocus/useHasFocus.test.tsx index f5bfed7..d780188 100644 --- a/src/hooks/useHasFocus/useHasFocus.test.tsx +++ b/src/hooks/useHasFocus/useHasFocus.test.tsx @@ -1,6 +1,6 @@ /* eslint-disable react/jsx-no-literals */ /* eslint-disable react/no-multi-comp */ -import { act, render, waitFor } from '@testing-library/react'; +import { act, render } from '@testing-library/react'; import { useRef, type ReactElement } from 'react'; import { useHasFocus } from './useHasFocus.js'; diff --git a/src/index.ts b/src/index.ts index 4670f9f..402a77a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -18,3 +18,4 @@ export * from './hooks/useToggle/useToggle.js'; export * from './hooks/useUnmount/useUnmount.js'; export * from './hooks/useWindowEventListener/useWindowEventListener.js'; export * from './utils/arrayRef/arrayRef.js'; +export * from './utils/objectFit/objectFit.js'; diff --git a/src/utils/objectFit/objectFit.mdx b/src/utils/objectFit/objectFit.mdx new file mode 100644 index 0000000..1a31acf --- /dev/null +++ b/src/utils/objectFit/objectFit.mdx @@ -0,0 +1,155 @@ +import { Meta } from '@storybook/blocks'; + + + +# objectFit + +This util mimics the CSS property `object-fit` for all HTML elements; + +It exports two reusable methods: `contain` and `cover`. Given the sizes of an parent element and its +child element: Contain returns the size to be applied to the element to let it fits its parent and +keeping its apect ratio. Cover returns the size to be applied to the element to let it fill its +parent, keeping its aspect ratio, most likely overflowing the parent element. + +If the sizes or aspect ratio are initially known, it's better to use values instead of retrieving +sizes from an image because its faster from a performance perspective. + +## Reference + +```ts +function objectFit(fit: 'contain' | 'cover') { + return ( + parentWidth: number, + parentHeight: number, + childWidth: number, + childHeight: number, + ): { x: number; y: number; width: number; height: number; scale: number; cssText: string } => { + if ([parentWidth, parentHeight, childWidth, childHeight].some((value) => value <= 0)) { + throw new Error(`All arguments should have a positive value`); + } + + const mathMethod = fit === 'contain' ? Math.min : Math.max; + const scale = mathMethod(parentWidth / childWidth, parentHeight / childHeight); + const width = Math.ceil(childWidth * scale); + const height = Math.ceil(childHeight * scale); + const x = Math.trunc((parentWidth - width) * 0.5); + const y = Math.trunc((parentHeight - height) * 0.5); + + return { + x, + y, + width, + height, + scale, + cssText: `left:${x}px;top:${y}px;width:${width}px;height:${height}px;`, + }; + }; +} + +export const contain = objectFit('contain'); +export const cover = objectFit('cover'); +``` + +### Parameters + +- parentWidth: number +- parentHeight: number +- childWidth: number +- childHeight: number + +### Returns + +An object containing: + +- x: number +- y: number +- width: number +- height: number +- scale: number +- cssText: string (easily add CSS values to child element) + +## Usage + +Contain: + +With the contain method you can use both position absolute and relative on the child element. +Relative can be useful if you want to position elements inside absolute to the parent. + +```tsx +import { contain } from './objectFit.js'; + +export function Contain(): ReactElement { + const parentRef = useRef(null); + const childRef = useRef(null); + + const onResize = useCallback(() => { + if (!parentRef.current || !childRef.current) { + return; + } + + const objectFit = contain(parentRef.current.offsetWidth, parentRef.current.offsetHeight, 1, 1); + + childRef.current.style.cssText += objectFit.cssText; + }, [parentRef, childRef]); + + useResizeObserver(parentRef, onResize); + + return ( +
+
+
+ ); +} +``` + +Cover: + +With contain you need to use position absolute to position the child. + +```tsx +import { cover } from './objectFit.js'; + +export function Cover(): ReactElement { + const parentRef = useRef(null); + const childRef = useRef(null); + + const onResize = useCallback(() => { + if (!parentRef.current || !childRef.current) { + return; + } + + const objectFit = cover( + parentRef.current.offsetWidth, + parentRef.current.offsetHeight, + 1920, + 1080, + ); + + childRef.current.style.cssText += objectFit.cssText; + }, [parentRef, childRef]); + + useResizeObserver(parentRef, onResize); + + return ( +
+
+
+ ); +} +``` diff --git a/src/utils/objectFit/objectFit.stories.tsx b/src/utils/objectFit/objectFit.stories.tsx new file mode 100644 index 0000000..86a8f00 --- /dev/null +++ b/src/utils/objectFit/objectFit.stories.tsx @@ -0,0 +1,101 @@ +/* eslint-disable react/no-multi-comp */ +import { type ReactElement, useCallback, useRef } from 'react'; +import { useResizeObserver } from '../../hooks/useResizeObserver/useResizeObserver.js'; +import { contain, cover } from './objectFit.js'; + +export default { + title: 'utils/objectFit', +}; + +export function Contain(): ReactElement { + const parentRef = useRef(null); + const childRef = useRef(null); + const infoRef = useRef(null); + + const onResize = useCallback(() => { + if (!parentRef.current || !childRef.current || !infoRef.current) { + return; + } + + const objectFit = contain(parentRef.current.offsetWidth, parentRef.current.offsetHeight, 1, 1); + childRef.current.style.cssText += objectFit.cssText; + + infoRef.current.innerHTML = JSON.stringify(objectFit); + }, [parentRef, childRef, infoRef]); + + useResizeObserver(parentRef, onResize); + + return ( +
+
+ +
+
+
+ ); +} + +export function Cover(): ReactElement { + const parentRef = useRef(null); + const childRef = useRef(null); + const infoRef = useRef(null); + const demoRef = useRef(null); + + const onResize = useCallback(() => { + if (!parentRef.current || !childRef.current || !infoRef.current || !demoRef.current) { + return; + } + + const objectFit = cover( + parentRef.current.offsetWidth, + parentRef.current.offsetHeight, + childRef.current.naturalWidth, + childRef.current.naturalHeight, + ); + childRef.current.style.cssText += objectFit.cssText; + + infoRef.current.innerHTML = JSON.stringify(objectFit); + // Using a second image to show the overflowing from the child element on the parent element because the css resize property doesn't allow overflowing. + const size = childRef.current.getBoundingClientRect(); + demoRef.current.style.cssText += `left:${size.left}px;width:${size.width}px;height:${size.height}px;`; + }, [parentRef, childRef, infoRef]); + + useResizeObserver(parentRef, onResize); + + const dataUri = + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAB4AAAAQ4CAIAAABnsVYUAAAszUlEQVR4nO3d21NV5/3H8b05uDeo4AHcKh4CajUiSWNr7enCNpP+Af1Pc9XOdDJNp/VQk0yamKqNh1YNoGgQAeW42b8LZ/itwSoo+xO1vF5XPA9rrf3lznnP8tnl8fHxEgAAAAAANFvL6x4AAAAAAID/TQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABECNAAAAAAAEQI0AAAAAAARAjQAAAAAABFtr3sAAABYl3q9/qc//enJkyelUun48ePHjh175Uc9ePDg7t274+Pj09PTCwsLjUZj06ZNHR0dO3furNVqtVqteVM/1+Li4r179+7du/fw4cO5ubn5+fnW1tZKpdLd3d3T07Nv375KpfIDjAEAAE1RHh8ff90zAADAq7t06dK1a9ee/vzKAXp4ePjy5ctTU1MvuKarq2toaCiXoev1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7m7uGAAA0HSO4AAAYEPr6en56KOPrl69evPmzXq9/rzLyuXy3r17T5w4sXnz5sQYe/fu/fDDD7/66qu7d+++4LJqtXrs2LH+/v6XfWMaAABeCwEaAICNrr29fWho6NixYyMjI2NjY48ePZqZmVlcXGxpaalUKlu3bu3t7e3r6wul52WbN2/+5S9/OTk5OTw8/ODBg+np6fn5+Uaj0d7e3tnZuWPHjlqtVqvVpGcAAN4i5fHx8dc9AwAAAAAA/4OcAQ0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQIQADQAAAABAhAANAAAAAECEAA0AAAAAQMT/AZN1LTqqap2hAAAAAElFTkSuQmCC'; + return ( +
+
+ +
+
+ +
+ ); +} diff --git a/src/utils/objectFit/objectFit.test.ts b/src/utils/objectFit/objectFit.test.ts new file mode 100644 index 0000000..ca84eaf --- /dev/null +++ b/src/utils/objectFit/objectFit.test.ts @@ -0,0 +1,37 @@ +import { contain, cover } from './objectFit.js'; + +describe('objectFit', () => { + describe('contain', () => { + it('returns expected values for positive input arguments', () => { + expect(contain(100, 100, 50, 50)).toEqual({ + x: 0, + y: 0, + width: 100, + height: 100, + scale: 2, + cssText: 'left:0px;top:0px;width:100px;height:100px;', + }); + }); + + it('throws an error for non-positive input arguments', () => { + expect(() => contain(1, 1, 1, 0)).toThrow('All arguments should have a positive value'); + }); + }); + + describe('cover', () => { + it('returns expected values for positive input arguments', () => { + expect(cover(100, 100, 50, 50)).toEqual({ + x: 0, + y: 0, + width: 100, + height: 100, + scale: 2, + cssText: 'left:0px;top:0px;width:100px;height:100px;', + }); + }); + + it('throws an error for non-positive input arguments', () => { + expect(() => cover(1, 1, -1, 1)).toThrow('All arguments should have a positive value'); + }); + }); +}); diff --git a/src/utils/objectFit/objectFit.ts b/src/utils/objectFit/objectFit.ts new file mode 100644 index 0000000..e40178f --- /dev/null +++ b/src/utils/objectFit/objectFit.ts @@ -0,0 +1,31 @@ +function objectFit(fit: 'contain' | 'cover') { + return ( + parentWidth: number, + parentHeight: number, + childWidth: number, + childHeight: number, + ): { x: number; y: number; width: number; height: number; scale: number; cssText: string } => { + if ([parentWidth, parentHeight, childWidth, childHeight].some((value) => value <= 0)) { + throw new Error(`All arguments should have a positive value`); + } + + const mathMethod = fit === 'contain' ? Math.min : Math.max; + const scale = mathMethod(parentWidth / childWidth, parentHeight / childHeight); + const width = Math.ceil(childWidth * scale); + const height = Math.ceil(childHeight * scale); + const x = Math.trunc((parentWidth - width) * 0.5); + const y = Math.trunc((parentHeight - height) * 0.5); + + return { + x, + y, + width, + height, + scale, + cssText: `left:${x}px;top:${y}px;width:${width}px;height:${height}px;`, + }; + }; +} + +export const contain = objectFit('contain'); +export const cover = objectFit('cover');