Skip to content

Instantly share code, notes, and snippets.

@deviationist
Last active April 7, 2024 12:49
Show Gist options
  • Save deviationist/8da01bbf62b96b0f8b95fdccca544489 to your computer and use it in GitHub Desktop.
Save deviationist/8da01bbf62b96b0f8b95fdccca544489 to your computer and use it in GitHub Desktop.
A function-based React-component that will scale font size up/down to use all available space inside a container. Uses Tailwind CSS.
import React, { useEffect, useState, useRef, useCallback } from 'react';
type Props = {
text: string
maxSize?: number
minSize?: number
maxHeight?: number
fontStepSize?: number
containerWidthMargin?: number
initialHidden?: boolean
}
const TextFit = ({
text,
maxSize = 100,
minSize = 1,
maxHeight,
fontStepSize = .1,
containerWidthMargin = 1,
initialHidden = true
}: Props) => {
const containerRef = useRef<HTMLDivElement>(null)
const textContainerRef = useRef<HTMLDivElement>(null)
const [isHidden, setIsHidden] = useState<boolean>(initialHidden)
const getCurrentFontSize = useCallback((): number => {
if (textContainerRef.current) {
return parseFloat(window.getComputedStyle(textContainerRef.current).fontSize);
}
return 0
}, [])
const decreaseFontSize = useCallback((): void => {
if (textContainerRef.current) {
textContainerRef.current.style.fontSize = (getCurrentFontSize() - fontStepSize) + 'px';
}
}, [])
const increaseFontSize = useCallback((): void => {
if (textContainerRef.current) {
textContainerRef.current.style.fontSize = (getCurrentFontSize() + fontStepSize) + 'px';
}
}, [])
const getContainerWidth = useCallback((): number => {
if (containerRef.current) {
return containerRef.current.offsetWidth - containerWidthMargin
}
return 0
}, [])
const getContainerHeight = useCallback((): number => {
if (containerRef.current) {
return containerRef.current.offsetHeight
}
return 0
}, [])
const getTextWidth = useCallback((): number => {
if (textContainerRef.current) {
return textContainerRef.current.offsetWidth
}
return 0
}, [])
const getTextHeight = useCallback((): number => {
if (textContainerRef.current) {
return textContainerRef.current.offsetHeight
}
return 0
}, [])
const getDiff = useCallback((): number => {
return getContainerWidth() - getTextWidth()
}, [])
const isTooHigh = useCallback((): boolean => {
if (!maxHeight) return false
return typeof maxHeight === 'number' && getTextHeight() > maxHeight
}, [])
const adjustFontSize = useCallback(() => {
if (containerRef.current && textContainerRef.current) {
if (isTooHigh()) {
while (isTooHigh() && getCurrentFontSize() > minSize) {
decreaseFontSize()
}
} else {
const xDiff = getDiff()
if (xDiff > 0) {
while (getDiff() > 0 && getCurrentFontSize() < maxSize && !isTooHigh()) {
increaseFontSize()
}
} else if (xDiff < 0) {
while (getDiff() < 0 && getCurrentFontSize() > minSize) {
decreaseFontSize()
}
}
}
setIsHidden(false)
}
}, [])
useEffect(() => {
adjustFontSize()
window.addEventListener('resize', adjustFontSize)
return () => window.removeEventListener('resize', adjustFontSize)
}, [text])
return (
<>
<div className={`${isHidden ? 'opacity-0' : ''} transition-all w-full overflows-hidden`} ref={containerRef}>
<span className='whitespace-nowrap box-content' ref={textContainerRef}>
{text}
</span>
</div>
</>
)
}
export default TextFit
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment