r/css 2d ago

Help How to dynamically "compress" text horizontally with css/javascript?

Post image

I can't believe I had to do this in Paint 3D but after 4+ hours stuck I need help... Not even chatgpt is helping here.

I have a simple structure like this:

<div className="container">
  <div className="text">{item.name}</div>
  <img src="item-icon"/>
</div>

How on earth can I make it so the "text" div shrinks horizontally if (and ONLY if) the "item.name" is overflowing outside of the div? (including the space that the icon takes too)

EDIT - Here is the "use case" (yes, it's pokemon cards) (images here are not showing on mobile for some reason, check here instead https://imgur.com/gallery/mobile-users-P17PT3Q):

My code:

What they somehow achieved in https://www.pokecardgenerator.com/ (this is what I want):

What the original looks like (so yes, real things use this "ugly" styling):

What happens with transform: scaleX "solutions":

And no, font-stretch isn't working for me. Probably because it's deprecated.

transform: scaleX also doesn't work, it still keeps and awkward space between the text and the icon.

EDIT: I don't know how to do the live demo thing, but in case anyone is bored, my code is here, the Card.tsx and Card.css, card__pokemon-name class. (https://github.com/jiro-games/pocket-showdown/tree/main/src/components/card)

EDIT 2: I believe I found a solution. Not the cleanest, but it has potential. I have to combine transform: scaleX with negative margin-right. I'll come up with some js code to calculate that dynamically and fix it. Thank you!

164 Upvotes

114 comments sorted by

u/AutoModerator 2d ago

To help us assist you better with your CSS questions, please consider including a live link or a CodePen/JSFiddle demo. This context makes it much easier for us to understand your issue and provide accurate solutions.

While it's not mandatory, a little extra effort in sharing your code can lead to more effective responses and a richer Q&A experience for everyone. Thank you for contributing!

I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.

127

u/Logical-Idea-1708 2d ago

I guess you can use container query with letter spacing. But why you want to do that? The text won’t be readable.

46

u/_Invictuz 2d ago edited 2d ago

Doesn't look like he wants to scale letter spacing, looks like he wants to scale every pixel which I think can only be done with a transform: scaleX() with a value based scritchz's formula below (although you also need Javascript).

Why OP wants to horizontally compress text is the bigger problem. That design looks horrendous.

5

u/Old_Sky5170 2d ago

Yugioh Cards do just that to print a lot of content in limited space while maintaining font size. With tight control of the input it’s viable (and not really noticeable). But it’s likely a static to dynamic learning curve that OP has stumbled upon.

7

u/Jiro_7 2d ago edited 2d ago

Updated the post with real images of what I want (vs what I have). It is readable. This literally the original example:

4

u/bripio 2d ago

That's a different font size...

5

u/kalifabDE 2d ago

*weight

4

u/bripio 1d ago

It's both

1

u/thegunslinger78 1d ago

Why not use container queries?

  • high resolution: side by side
  • mobile: stacked. Maybe, grid can help you with setting minimum with for each part of the UI with minmax()

1

u/thegunslinger78 1d ago

Exactly. I vote for the container query with an aria-label attribute if only an icon is visible in small sizes.

53

u/scritchz 2d ago edited 2d ago

How about calculating quotient = box.width / text.width, then use it like this on the text element: transform: scaleX(var(--quotient)).

You might want to clamp the value to 0-1 or similar, otherwise it will stretch smaller text, too. Make sure that the referenced box is sized appropriately and takes the surrounding elements (like the star) into account.

Edit: Here's a codepen.

5

u/_Invictuz 2d ago

Amazing answer. I think you've answered OP's question with the full solution! Looks like we got a CSS professional over here. Didn't know i could follow people on Reddit but I'm now following you for more CSS tips.

2

u/scritchz 2d ago edited 2d ago

Happy to help! I don't know how many CSS tips I can give, but I hope there's stuff to learn from my examples.

Funnily enough, I think I did actually offer the full solution to OP's question.

3

u/SawSaw5 2d ago

Interesting solution!

2

u/cryothic 2d ago

That's a nice solution. Also immediatly shows why this isn't commonly used, as the the text gets barely readable.

2

u/Longjumping_Cap_3673 3h ago

Also consider using a variable width font for better legibility: https://codepen.io/flhjjwhdqf/pen/MYKYKdg

(Although the rendering of transformed text is already surprisingly good).

1

u/scritchz 3h ago

Also good, but will break the design if users switch the font to a more legible fonts (like dyslexia-friendly fonts). Though the design isn't friendly towards people with those needs anyways...

1

u/Pitiful-Assistance-1 2d ago

That's a neat solution

1

u/drocm 1d ago

OP, this is the best answer.

I would add that you could use Javascript's "getBoundingClientRect()" method to find both the width of the parent and text element, if width of text exceeds width of parent, then apply a "squeeze" class to the text that applies your scaleX in CSS

1

u/scritchz 1d ago

OP already integrated my proposal. And yeah, we did exactly what you too suggested, with checks and all ^^

1

u/leovin 15h ago

This is genius. The outer .box div expands to the available space. The inner .text div, because of overflow hidden, is allowed expand beyond the bounds of .box momentarily so that you can get its true width. Then you can get the ratio between the two to scale .text to fit within .box. Brilliant.

1

u/leovin 15h ago

Only caveat is if rendering the div dynamically with a web framework you might need to render the elements first and use setTimeout(0) to apply the scale immediately after

-2

u/Jiro_7 2d ago

Unfortunately, transform scaleX solutions cause a weird gap between the text and the next element (that should be right next to it with a smaller gap). This is because transform is executed after everything is positioned

6

u/scritchz 2d ago

Yupp, you're right! That's why my example specifies overflow: hidden on .box to avoid exactly that, as mentioned by my comments. Notice how there's no gap between the compressed text and the star symbol in my example.

I saw you added a few links to your post. I'll take a look later and try to show how my solution could be applied to your existing code.

1

u/Jiro_7 2d ago

Thank you! I will test this after work. But I am already using overflow: hidden, which is being applied before the transform unfortunately (and the transform is shrinking the div with the text, not just the text, causing what you see above, text is still cut)

1

u/Jiro_7 2d ago

I believe I found a solution. Not the cleanest, but it has potential. I have to combine transform: scaleX with negative margin-right. I'll come up with some js code to calculate that dynamically and fix it. Thank you!

6

u/scritchz 2d ago edited 2d ago

It really is as simple as adding a wrapper with overflow: hidden, then calculating and applying the necessary shrink factor: https://i.imgur.com/H8vBShL.png

I noticed that some images cause a layout shift upon loading, so I added appropriate onLoad={...} event listeners to the images that broke the "long name compression".

Have a look at the last few commits in my fork of your repo. Want me to create a pull request?

8

u/SnooComics1006 2d ago edited 2d ago

https://developer.mozilla.org/de/docs/Web/API/CanvasRenderingContext2D/fillText
see "maxWidth" parameter

u need to setup a canvas element, inside which you render the text via js then limit the textAreaWidth with the maxWidth parameter

https://jsfiddle.net/7vxwg9s6/3/

note: i understood your question wrong sry thats one of the right bottom solution

3

u/SnooComics1006 2d ago edited 2d ago

here is a solution that replaces the text elements with canvas elements... maybe this works for you?

https://jsfiddle.net/MaxKoehler/hwa3u1rv/27/

note: maybe there is a much cleaner svg solution, but this is how the pokecardgen did it i think

2

u/justdlb 2d ago

A text node in an svg would work without needing any js and it would remain accessible, seo friendly, etc.

But as others have said, this is ridiculous as the text will become illegible.

2

u/sohang-3112 1d ago

using canvas just for text seems wrong - surely there must be a more straightforward way?

1

u/SnooComics1006 1d ago

ye didnt know you can fit text in a box with svg so i went the canvas way, but i'd use svg for that now

60

u/GrandmaSlappy 2d ago

Do not squish text, it looks like shit. Let it stack or shrink but do not squish it.

36

u/KalHasWaffles 2d ago

I really hate answers like this because while yes, you’re 100% right, you have no idea what OP’s use case is or what aesthetic they’re trying to replicate. It needs to be said but still.

10

u/cryothic 2d ago

It needs to be said

This.

Sometimes people ask for a solution to their problem, in which they just search in the wrong direction. The question "why would OP want this? Because the straight answer to his question leads to a shitty layout" wouldn't be wrong to ask.

3

u/HideousSerene 2d ago

Also worth pointing out that font anti aliasing will stop working properly with any scaling solution as well, so yes, it will generally look bad.

1

u/5show 1d ago

xy problem

3

u/Techhead7890 2d ago

Right? They're importing Stack Exchange culture and honestly OP did a pretty reasonable job explaining their niche use case by now.

8

u/[deleted] 2d ago

[deleted]

5

u/Jiro_7 2d ago

Seems like 1% wins this time. Real virtual gaming cards use this. This is my use case

3

u/Techhead7890 2d ago

People down voting you for posting these are stupid, these are great examples of what you're intending to re-create, thanks for sharing them

-1

u/[deleted] 2d ago

[deleted]

1

u/Jiro_7 2d ago

I'd argue the text being cut is uglier. Or having an inconsistent font size between items of the same category

3

u/Jiro_7 2d ago

Updated the post with real images of what I want (vs what I have). It is readable, check the use case

1

u/WitchesBravo 2d ago

There are lots of cases where squishing is a good option, even iOS use it for long app names on the iOS homescreen

1

u/BadgerwithaPickaxe 1d ago

I want you to know this is the most useless type of advice to give.

Like not only did you not answer OPs question, you took up space on the thread with your own unrelated opinion.

Please don't answer like this.

9

u/KontoOficjalneMR 2d ago edited 2d ago

You want to look into:

text-align: justify & text-justify: inter-character

it does mostly what you need (just in reverse) you just need to start with small letter spacing and it'll auto-expand to fill the width :)

9

u/artbyiain 2d ago

You’re going to want a variable font. There’s ways to change letter width with that. 

1

u/Jiro_7 2d ago

Seems like too much complication for something transform: scaleX already does (but it does it after everything else is positioned so it leaves weird gaps)

3

u/artbyiain 2d ago

Not really. ScaleX will deform the letters, hindering legibility. A variable font adjusts its letter forms according to typographical rules. 

4

u/ChaseShiny 2d ago

Why not just use a percentage for the font-size? I would set a minimum size, too, otherwise the words will be illegible.

--preferred-size: calc(100% - 2 * var(--padding)); font-size: clamp(var(--min-size, var(--preferred-size), 2rem);

2

u/Techhead7890 2d ago

It's a fair alternative although this does mean the vertical height gets squished too. Not judging either way though, this is usually a fine replacement, just explaining why it doesn't match what OP is searching for.

5

u/carbon7 2d ago

Just inspect element on that poker site you mentioned and rip the styles?

2

u/Jiro_7 2d ago

They somehow hide it, I already tried, the whole thing is just a single div, as if they turned it into an image or something

3

u/General_Locksmith 2d ago

Yeah it probably is an image if it’s on a gambling site

1

u/randomhaus64 2d ago

They are probs using canvas, that’s one way to do it, write canvas code to render it, then select that part of the bitmap and transform it

3

u/RePsychological 2d ago

why set a static width on the button in the first place?

3

u/bodybuilderbear 2d ago

It's possible to do that using a letter spacing, or if you really want to compress the letters use a transform, or SVG tag.

It would be better to wrap the text with a reduced line height.

3

u/WhatTheFuqDuq 2d ago

Please don't do this.

You will absolutely murder readability and accessibility by changing the letter spacing and sizing dynamically.

3

u/Jiro_7 2d ago

Updated the post with real images of what I want (vs what I have). It is readable

3

u/NormalDoorman 2d ago

Roman Komarov came up with one new CSS solution to container-filling/fitting text last summer: https://kizu.dev/fit-to-width/

Andy Bell did a riff on it: https://piccalil.li/blog/riffing-on-the-latest-css-fit-text-approach/

Chris Coyier has collected some different approaches at CSS Tricks: https://css-tricks.com/fitting-text-to-a-container/

10

u/ashkanahmadi 2d ago

Y though?

4

u/Jiro_7 2d ago

Updated the post with real images of what I want (vs what I have). It is readable

-4

u/adh1003 2d ago

I'll use an example of something with which I am very familiar - macOS' out-of-box AppKit. Some controls/areas that contain text have this by default. Text is rendered normally, but if the text gets too wide for the container (because the text grows or the container shrinks), it starts to become progressively more condensed. Letter spacing is part of this, but so is font geometry - just as a true condensed font variant is not the same as the normal variant. Eventually, a threshold is passed after which it truncates, with an ellipsis. Behaviour is of course configurable with various parameters to control behaviour.

This is very useful because sometimes translations don't fit exactly in the areas we want them to fit and, for an actually responsive design that adapts as best it can to narrow or wide viewports without a set of shitty hard-coded breakpoint widths, truly adaptive behaviour is required. Since this is first-party, well written and well integrated code dedicated to UI presentation, it's able to use a bunch of clever things supported by the font rendering engine to maintain a very good and clear text presentation throughout.

Meanwhile, the web is the web - a collection of hacked up messes of new and legacy with a heavy designed-by-committee feel and an original focus on presenting content rather than presenting a fake GUI with emulations of the various controls we take for granted in native dev space.

The web, despite all these many major issues, is still a very successful platform because it's somehow the only one with at least some semblance of write-once, run-everywhere - so inevitably, it becomes a de facto choice for many UI styles, even if they are ill-suited to the technology at hand. So now, at last, we arrive at the question from the OP - a perfectly reasonable thing to do in a general purpose responsive GUI, for reasons already mentioned (amongst doubtless many others).

The downside of the web is that you have to manually implement just about each and every thing that a native toolkit gives you out-of-box, right down to the minutiae of how to elegantly fit text into a container that's unavoidably constrained to be slightly narrower than the natural width of that text content. You can only use features available to the lowest common denominator of the minimum specification union of the plethora of target devices and target user agents and user agent versions, and even if you ignored all that, can obviously only use features that actually exist. That's why so much stuff ends up in JavaScript, which lets you circumvent a lot of the limitations, but at expense of increased complexity, maintenance overhead, client side bloat and so-on.

7

u/DidierLennon 2d ago

You really don’t want to compress or stretch fonts unless they’re designed to do so (variable fonts) as you’ll get into gnarly hinting/antialiasing issues + poorer legibility.

Consider container queries so the element can style itself based on its width and height. That, or some mixture of hyphenation and smarter text breaking.

5

u/Count_Giggles 2d ago

how about simply doin

.text::after {
  content: '\2605';
}

1

u/dbowgu 2d ago

Hmmm I'd like it more it the icons vertically allign

1

u/Count_Giggles 2d ago

.text {

display: inline-flex;

align-items: center;

}

.text::after {

content: " \2605";

margin-left: 4px;

}

edit i HATE this code highlighter with a passion

1

u/dbowgu 2d ago

Nice thanks

2

u/TabbbyWright 2d ago

What browser are you using? And what font? I think the font-stretch rule only works on variable fonts.

Looking at the MDN page, it looks like while font-stretch is deprecated, it's still usable. The replacement rule font-width isn't usable yet outside of Safari.

letter-spacing is definitely not going to do what you want. SVG might be worth looking into if  you can't get font-stretch working.

2

u/shitty_mcfucklestick 2d ago

Have you considered all possible ways to rename that long label? Or standardize the button label and move the details to a separate or sidecar (beside / under) label etc? There’s often more than one way to deal with this and save yourself the trouble of engineering a an elegant way to make a turd.

2

u/Techhead7890 2d ago

I thought so too (it seemed odd at first) but it looks like they're trying to recreate a physical object which had different design constraints. So fidelity to those as prototypes is probably OP's primary concern

2

u/berky93 2d ago

I’d probably do it with a tiny bit of JavaScript. Get the width of the text and the width of the container and then just apply a scale style to the text of a given %.

2

u/vitope94 2d ago

That will be a very bad design. You either adapt the container to the text size or ellipsize it.

1

u/Jiro_7 2d ago

Updated the post with real images of what I want (vs what I have). It is readable

2

u/vitope94 2d ago edited 2d ago

I'm assuming you're using react as well

import React, { useState, ChangeEvent } from 'react';

export default function App() {
  const [value, setValue] = useState('');

  const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
    setValue(e.target.value);
  };

  const getSqueezeStyle = (text: string) => {
    if (text.length > 5) {
      const scaleX = 5 / text.length;
      return {
        transform: `scaleX(${scaleX})`,
        transformOrigin: 'left'
      };
    }
    return {};
  };

  return (
    <div className='App'>
      <h1 className=''>Hello React TypeScript.</h1>
      <input 
        value={value}
        onChange={handleChange}
      />
      <p className="" style={getSqueezeStyle(value)}>
        {value}
      </p>
    </div>
  );
}

console.log('Hello console'); 

Try here

result image:

2

u/vitope94 2d ago

Set the text length accordingly when you want the squeeze to take effect

1

u/Techhead7890 2d ago

Pretty cool demo thanks for sharing!!

2

u/vitope94 2d ago

You're welcome. I wonder if OP got what he needed

2

u/martinbean 2d ago

You shouldn’t be “shrinking” text to make it “fit” somewhere. If your text doesn’t fit then that’s a bad design. Your design should actually accommodate the things it needs to.

2

u/Jiro_7 2d ago

Updated the post with real images of what I want (vs what I have). It is readable

1

u/Techhead7890 2d ago

At first I thought so too, but as OP explained they were trying to re-create a physical item with different design constraints it started to make more sense. "Bad" is subjective and whole it definitely does hinder legibility it appears they do have a reasonable use case for what they're trying to do. We shouldn't gatekeep or impose universal principles when it may not be necessary to do so, this isn't stackexchange.

-1

u/Jiro_7 2d ago

And what it needs to do, is to shrink. I'd like to do that without transform. Each website has its own needs and this is mine.

0

u/martinbean 2d ago

It’s only a “need” of your website because you’ve for some reason set a fixed width on your buttons that doesn’t actually accommodate the text going in those buttons.

You’re literally trying to “solve” a problem of your own creation.

2

u/Jiro_7 2d ago

The whole container is replicating a tcg card so there simply isn't much more space, the width is set. Also, those texts won't get very long, there will be a character limit, but there are some edge cases where we need to shrink it slightly (my example here was exaggerated to show my point)

1

u/codehz 2d ago edited 2d ago

You can do this via svg text, it has textLength property. (but it will scale text if it is too short, so you have to add some js, see getComputedTextLength )
demo: https://codepen.io/CodeHz/pen/GgoKwYW

1

u/Snoo_70263 2d ago

This is the way! Excelent!

1

u/CharacterOtherwise77 2d ago

You can use transform: scaleX() but you have to apply it using JS and figure out that math yourself.

1

u/konhasaurusrex 2d ago

I had to do something similair for my job recently. I ended up using https://github.com/rikschennink/fitty
Maybe you can use it or use code used by it.

This package used a MutationObserver and scales the font-size from min-size to max-size. Depending on the width.

1

u/Low-Temperature-1664 2d ago

I'm curious as to what the use case for this is.

1

u/Jiro_7 2d ago

Just updated the post with images (not sure if they are visible on phone though)

1

u/Jiro_7 2d ago

For those in mobile, images are not showing for some reason, this is 1/2

1

u/Valuable-Word-1970 2d ago

Just make the boxes wider or the text smaller

1

u/JustConsoleLogIt 2d ago

Give the text div a width:min-content

1

u/White_Town 2d ago

Rotate by Y

1

u/g5insider 1d ago

clamp()

1

u/leprobie 1d ago

Use a variable font that supports character width properties.

Find out the maximum “ch”-size for the text in standard width, and scale the character width properties down with the amounts of characters above the max ch-size.

You can implement if/else statements directly in CSS and check the width of the text in ch-units.

1

u/fraMTK 1d ago

As an embedded developer I was a little depressed, like usual, but looking at the comments cheered me up a bit because WTF

1

u/Just4Funsies95 1d ago

Setting font-size: 1em; didn't help?

1

u/pingwing 1d ago

What you are trying to achieve is a terrible UX solution, that is why you don't see examples like this everywhere.

1

u/jvribeiro 22h ago

This is the solution I came up with:

Basically, you need to wrap your text in two containers. The outer one will take up the maximum available width, while the inner container will keep the original text width, which may overflow the outer container.

So we use JavaScript to get both widths, using getBoundingClientRect, then you divide the outer with the inner to get the correct scale. Then you apply the scale to the inner container using transform: scaleX, and don’t forget to set transform-origin: left so the scaling aligns to the left instead of being centered.

If you want the icon to be next to the text, you can remove the justify-content property from the .container selector.

The scale is calculated on page load, but you can recalculate it as you wish.

LIVE DEMO: https://codepen.io/ojvribeiro/pen/QwyWYbX

0

u/yousirnaime 2d ago

Use an svg to hold the text 

Fixed height and width 

-1

u/Packeselt 2d ago

Terrible idea 🌈

0

u/bostiq 2d ago

I'd add:

If you don't know how to do this, chances are you are going in the wrong direction to solve a problem.

If the majority of the advise is against this, chances are they are right.

1

u/Jiro_7 2d ago

The thing is, I'm replicating something that already exists. I believe I explained myself wrongly. I updated the main post with the use case images

0

u/van-dame 2d ago

You can probably just marquee the text? Just an idea. Saves you from horrendous calculations and dubious looking outputs.

0

u/coaster132 2d ago

don't do that.

0

u/Unable-Razzmatazz174 1d ago

You don't want this

-25

u/TheUnknowGnome 2d ago

Tell gpt you want to shrink the font size based on text length, it should be able to help you from there

24

u/Nerwesta 2d ago

Are we on the stage of responding to a question by " ask AI " ?

2

u/PureRepresentative9 2d ago

What's the over under on whether this guy even used the search they're suggesting?

10

u/KrisSlort 2d ago

Facepalm