posted: 2019/10/13

内部要素のサイズに合わせてfont-sizeを調整するcomponentをhooksで作る

作ったものメモ。
ふとiOSでadjustsFontSizeToFitWidthという横幅に合わせてフォントサイズが変わるのがあったな、というのを思い出したので再現してみた。
import React, { useState, useRef, useLayoutEffect, useEffect } from "react"
import stringWidth from "string-width"
import styled from "styled-components"

const Container = styled.div`
  width: 100%;
`

const AutoSizedButton = ({ text }) => {
  const ref = useRef()
  const [width, setWidth] = useState(0)
  const [fontSize, setFontSize] = useState("auto")
  useEffect(() => {
    const sizePx = (width / stringWidth(text)) * 2
    setFontSize(`${sizePx}px`)
  }, [width, text])

  useLayoutEffect(() => {
    // @ts-ignore
    const obs = new ResizeObserver((e) => setWidth(e[0].contentRect.width))
    obs.observe(ref.current)
    return () => obs.disconnect()

  }, [])
  return (
    <Container ref={ref} fontSize={fontSize}>
      {text}
    </Container>
  )
}
ResizeObserverを利用している。
まだこれも不安定なツールなので、そこまで実用的なものではない。
フォントサイズの計算として、string-widthを利用している。昔はencodeURIComponentでの計算方法などがあったが、今は3byteで判定されるので使えなくなっている。
カスタムhooksとして切り出すならこんな感じだろう
(が、refsを渡すのはいまいちなので切り出しに向いてない)
const useAutoFontSize = (targetRef, text) => {
  const [width, setWidth] = useState(0)
  const [fontSize, setFontSize] = useState("auto")
  useEffect(() => {
    const sizePx = (width / stringWidth(text)) * 2
    setFontSize(`${sizePx}px`)
  }, [width, text])
  
  useLayoutEffect(() => {
    // @ts-ignore
    const obs = new ResizeObserver((e) => setWidth(e[0].contentRect.width))
    obs.observe(targetRef.current)
    return () => obs.disconnect()
  }, [])
  return fontSize
}
Edit on Github