@flyyer/use-fit-text

React hook used to fit text in a div

Usage no npm install needed!

<script type="module">
  import flyyerUseFitText from 'https://cdn.skypack.dev/@flyyer/use-fit-text';
</script>

README

@flyyer/use-fit-text

React hook that iteratively adjusts the font size so that text will fit in a div.

  • checks if text is overflowing by using scrollHeight and offsetHeight
  • recalculates when container is resized (using (polyfilled) ResizeObserver)
  • recalculates when content changes
  • uses binary search; with default options, makes a maximum of 5 adjustments with a resolution of 5% font size from 20-100%
  • < 4 kB minified + gzipped
  • written in TypeScript

Installation

This module is meant for Flyyer.io templates to generate dynamic og:images for your links. But you can use it in other projects if it fits your needs.

yarn add @flyyer/use-fit-text

Usage

import React from "react";
import clsx from "clsx";
import useFitText from "@flyyer/use-fit-text";

// Example for a flyyer.io template: $ npm create flyyer-app
export default function ExampleTemplate({ variables }) {
  const title = variables["title"];
  const description = variables["description"];

  // UI-dependent variables (title and description) are added to the hook's dependency array.
  const { fontSize, ref, isCalculating } = useFitText(
    {
      /** Depends on body's fontSize (usually 16px) */
      maxFontSize: 1000, // 1000%
      /** Lower values are more strict but causes more renders, defaults to `5` */
      resolution: 10,
    },
    [title, description],
  );

  const className = clsx({ "flyyer-wait": isCalculating }) // use class flyyer-wait to prevent eager renders of flyyer templates.
  return (
    <div ref={ref} style={{ fontSize, height: 40, width: 100 }} className={className}>
      <h1 style={{ fontSize: "2em" }}>
        {title}
      </h1>
      <p style={{ fontSize: "1.2em" }}>
        {description}
      </p>
    </div>
  );
}

If your text also depends on other style variables such as fontFamily, letterSpacing, etc; you should add them to the dependency array:

const title = variables["title"];
const fontFamily = variables["fontFamily"];
const { fontSize, ref, isCalculating } = useFitText({}, [title, fontFamily, letterSpacing]);
// ...

About

This project is a fork from saltycrane/use-fit-text.

The main difference is:

  • The original project depends on the innerHTML of the ref element to check for changes.
  • This fork has an explicit dependency array (same concept as useEffect(, [dependencies])).

The main benefic is explicit control and easier to trigger re-calculations.

API

useFitText(options)

  • Returns an object with the following properties:

    • fontSize (string) - the font size as a string (CSS percent) to be passed as the fontSize property of the style prop of the div
    • ref (React.MutableRefObject<HTMLDivElement>) - the ref to be passed to the ref attribute of the div
  • options (optional) - an object with the following optional properties:

    • logLevel (string, default: info) - one of debug, info, warn, error, or none
    • maxFontSize (number, default: 100) - maximum font size in percent
    • minFontSize (number, default: 20) - minimum font size in percent
    • onFinish ((fontSize: number) => void, default: undefined) - function that is called when resizing finishes. The final fontSize is passed to the function as an argument.
    • onStart (() => void, default: undefined) - function that is called when resizing starts
    • resolution (number, default: 5) - font size resolution to adjust to in percent

Questions

  • Why doesn't it work with Flexbox justify-content: flex-end;? This appears to be a bug with Flexbox. Try using CSS Grid or margin-top: auto;
  • What does the "reached minFontSize = 20 without fitting text" message in the console mean? This means use-fit-text was not able to fit the text using the minFontSize setting of 20. To ensure the text fits, set minFontSize to a smaller value.