javascripthtmlcssreactjsmomentjs

prevent dynamic text from moving layout


I am doing a small countdown project.

<div className={aboutPage ? styles['clock-container-aboutpage'] : styles['clock-container']}>
    <p className={styles['clock-container__p']}>
        <span>{years}</span> {years === 1 ? 'year, ' : 'years, '}
        <span>{months}</span> {months === 1 ? 'month, ' : 'months, '}
        <span>{days}</span> {days === 1 ? 'day' : 'days'}
    </p>
    <p className={styles['clock-container__p']}>
        <span className={timeSpaceFixCSS}>{padNum(hours)}</span>  {hours === 1 ? 'hour : ' : 'hours : '}
        <span className={timeSpaceFixCSS}>{padNum(minutes)}</span>  {minutes === 1 ? 'minute : ' : 'minutes : '}
        <span className={timeSpaceFixCSS}>{padNum(seconds)}</span> {seconds === 1 ? 'second' : 'seconds'}
    </p>
</div>

As you can see by looking at the countdown, the text moves a bit when numbers change due to the difference in symbol width. I want numbers to be fixed in place so they won't move anything.

At first I tried making font nums in the font monospaced, it worked but still if the font is to change, then it will break.

Then I tried wrapping numbers in a span with fixed width, but the catch is the website is adaptive and changes on viewport size, so on smaller screens the font becomes smaller and fixed-size span doesn't work.

How can I dynamically calculate font width and add some extra space to the wrapping span to keep the numbers in place? Or maybe there are other approaches to handle this?

Fixed it by adding timeSpaceFixCSS class:

<div className={aboutPage ? styles['clock-container-aboutpage'] : styles['clock-container']}>
    <p className={styles['clock-container__p']}>
        <span className={timeSpaceFixCSS}>{years}</span> {years === 1 ? 'year, ' : 'years, '}
        <span className={timeSpaceFixCSS}>{months}</span> {months === 1 ? 'month, ' : 'months, '}
        <span className={timeSpaceFixCSS}>{days}</span> {days === 1 ? 'day' : 'days'}
    </p>
    <p className={styles['clock-container__p']}>
        <span className={timeSpaceFixCSS}>{padNum(hours)}</span>  {hours === 1 ? 'hour : ' : 'hours : '}
        <span className={timeSpaceFixCSS}>{padNum(minutes)}</span>  {minutes === 1 ? 'minute : ' : 'minutes : '}
        <span className={timeSpaceFixCSS}>{padNum(seconds)}</span> {seconds === 1 ? 'second' : 'seconds'}
    </p>
</div>

timeSpaceFixCSS: const timeSpaceFixCSS = aboutPage ? styles['time-space-fix-aboutpage'] : styles['time-space-fix']

.time-space-fix, .time-space-fix-aboutpage {
    display: inline-block;
    text-align: center;
    font-family: inherit;
    font-size: inherit;
}

.time-space-fix {
    width: 3.6rem;
}

@media (max-width: 1535px) {
    .clock-container {
        font-size: 2.5rem;
    }

    .time-space-fix {
        width: 3rem;
    }
}

Solution

  • You can define the width of an element relative to the font-size of the root element using the rem unit.

    Just make sure to also define the font-size of the innerText in relative size so it does not break based on changes to the root font-size.

    My recommendation:
    When I look at your site, I rather recommend to wrap the individual clock elements of
    X hours : Y minutes : Z seconds
    in 3 individual elements with fixed relative width and display: inline-block; so the value-unit pairs are not separated and stay together on one line when the line breaks for different viewport sizes.

    See this post for more info: Prevent line-break of span element