2

My requirements are:

  1. From 1280 to 1920, font size should be fluid 14px to 22px
  2. Users should be able to zoom as normal

I haven't found a solution that actually does both. Yes I've seen CSS Tricks simplified fluid, using vw of any kind renders zoom ineffective.

  • clamp(0.875em, 1.146vw, 1.375em) - doesn't scale a the same rate as regular zoom, it's like 5% instead of 50%.
  • clamp(0.875em, 1.4vw - 0.3em, 1.375em) - runs into the min and max but shrinks on zoom.
  • calc(0.875em + 8 * (100vw - 1280) / 640) - doesn't zoom.
Roy
  • 7,811
  • 4
  • 24
  • 47
Kaijin
  • 53
  • 1
  • 1
  • 10

2 Answers2

0

In CSS calculations have limitations e.g. multiplication require one of the numbers to be unitless ref. So the formula which we were targeting can't be achieved in CSS:

clamp(14px, calc(1.09375vw + (0.05208vw * (100vw - 1280px) / 640)), 22px)

To cover all requirements we need handle this using JavaScript:

let element = document.querySelector('.scale');

function updateFontSize() {
  let size = 14;
  let vpWidth = window.innerWidth;
  let zoom = ((window.outerWidth) / window.innerWidth);

  if (vpWidth >= 1920) {
    size = 22;
  } else if (vpWidth <= 1280) {
    size = 14;
  } else {
    //calculate
    size = 14 + (vpWidth - 1280) * 8 / 640;
  }
  //apply zoom
  size *= zoom;

  element.style.setProperty('--font-size', size + 'px');

  document.getElementById('stats').innerText = 'Viewport Width: ' + vpWidth + 'px  \nZoom: ' + (zoom.toFixed(2) * 100).toFixed(0) + '%';
}


window.addEventListener('resize', () => updateFontSize());
updateFontSize();
p {
  margin: 0;
  padding: 0;
}

.f14 {
  font-size: 14px;
}

.f22 {
  font-size: 1.376rem;
}

.scale {
  --font-size: 14px;
  font-size: var(--font-size);
}

@media screen and (min-width: 1280px) and (max-width: 1980px) {
  body {
    background-color: wheat;
  }
}
<div id=stats></div>
<hr><br>
<p class=f14>Lorem ipsum dolor sit amet.| 14px</p>
<p class=scale>Lorem ipsum dolor sit amet.| scaling</p>
<p class=f22>Lorem ipsum dolor sit amet.| 22px</p>

Because StackOverflow puts output in iframe you won't see actual result running the snippet here. Switch to Full Page or run the code in separate local HTML file.
The way I found zoom level in above code requires code not placed in iframe. Finding zoom level of browser is not an easy task. See the thread for more info.

I didn't get the perceived vs required font size at 150% zoom. Also, let us know what should happen if user zooms out to 50%.

the Hutt
  • 16,980
  • 2
  • 14
  • 44
  • Unfortunately, this does not meet either requirement. At 1920, the font size is 21.0521, not 22. Zoomed in to 150%, font size is clamped at 14. the viewport scales down when zooming. 14 * 150% = 21px percieved zoom 22 * 150% = 33px required zoom – Kaijin Jan 04 '22 at 18:37
  • I think pure CSS solution won't be possible we need to use JavaScript. Can you give use cases for font size for 1) 1280 wide viewport zoomed to 150% and 2) 1920 wide viewport zoomed to 150%. What should happen on zoom out to 50%? – the Hutt Jan 05 '22 at 11:42
  • 1280 viewport = 14px + zoom 150% = 21px 1920 viewport = 22px + zoom 150% = 33px 1280 viewport = 14px - zoom 50% = 7px 1920 viewport = 22px - zoom 50% = 11px – Kaijin Jan 13 '22 at 23:53
0

I think it's not possible to achieve it with CSS only, but even with JavaScript it's quite a straightforward solution. Please let me know if I misunderstood something from the requirements.

Solution

I'm resetting the --fluid-size CSS variable at load & resize events by running the resetFluidSize function (notice this process will hurt the performance as it triggers reflows every time it runs, I'm not doing any optimization here in order to keep it simple).

Then I used the outerWidth to prevent the zoom level (which affects the innerWidth) from messing up the proper scale so no matter the page zoom, the computed style will be the desired one.

After doing the math, I update the CSS variable with document.body.style.setProperty.

Note

In order to see the right values, run the code snippet Full page.

window.addEventListener('load', () => {
  resetFluidSize();
  window.addEventListener('resize', resetFluidSize);
});

const
  a = 14,
  b = 22;

function resetFluidSize() {
  const
    factor = (window.outerWidth - 1280) / 640,
    lerp = a + (b - a) * factor,
    clamp = Math.max(a, Math.min(b, lerp)),
    fluidSize = `${clamp}px`;

  document.body.style.setProperty('--fluid-size', fluidSize);

  /* Just to update the feedback. */
  document.getElementById('fluid-text').setAttribute('currentSize', fluidSize);
}
:root {
  --fluid-size: 16px;
}

#fluid-text {
  font-size: var(--fluid-size);
}


/* Ignore the style below, its just for the feedback. */

p {
  border-right: 1px solid black;
  margin: 0;
  width: fit-content;
  position: relative;
}

p::after {
  content: attr(currentSize);
  position: absolute;
  left: 100%;
}
<body>
  <p style="font-size: 14px;" currentSize="14px">
    Lorem ipsum
  </p>
  <p id="fluid-text">
    Lorem ipsum
  </p>
  <p style="font-size: 22px;" currentSize="22px">
    Lorem ipsum
  </p>
</body>

Edit

If you want to run it locally, you can use this index.html (a GitHub Gist).

Leo
  • 849
  • 7
  • 20