Font-size and device-pixel-ratio - css

Let's say that I have default font size set to 16px (this should be redundant since it's default in most browsers):
For brevity, my site contains some box with size 10em x 10em.
body, html
{
font-size: 16px;
}
.box {
border: 1px green solid;
background-color:green;
width:10em;
height:10em;
}
My site is already responsive with min-width media queries for specific breakpoints targetted at em values (36, 62 and 85em).
The only thing my site does is change width and color of the box.
#media (min-width:36em) {
.box {
width: 36em;
border:1px red solid;
background-color:red;
}
}
#media (min-width:62em) {
.box {
width: 62em;
border:1px blue solid;
background-color:blue;
}
}
#media (min-width:85em) {
.box {
width: 85em;
border:1px orange solid;
background-color:orange;
}
}
Now let's say that I have some device with resolution 1440x900 where browsers decide that the device's pixel density is 2dppx by default. As result, everything looks much bigger than it should be. And I don't like it.
But the solution should be easy, right? Just add a media query at start for 2dppx pixel density and since it's twice as much pixels, I just reduce the font size by half...that should work, right?
And since I use "em" on everything then by changing the font-size by half I automatically reduce size of everything "em"-sized by half...right?
#media ( min-resolution: 2dppx) {
body, html {
font-size: 50%;
}
}
relevant jsFiddle
Well I found it does not work as I thought it would...I tried to google answers but did not find anything relevant, I suspect I misunderstand how font-sizes, ems and pixel density works...
Reducing font-size by 50% reduces the font-size by much more than by half. The original size is 16px but font-size: 50%; results in a font much smaller than font-size:8px ... Tested in Firefox and Chrome. Does anyone know why is that?
When I set font-size:8px; in the media query for 2dppx then media queries for min-width:36em, 62em and 85em do not trigger as I would have expected, at least when I change layout.css.devPixelsPerPx in Firefox. The min-width in media query behaves like the font-size is still at 16px eventhough it's changed with the dppx media query. Tested in Firefox and Chrome. Does anyone know why is that?
UPDATE
Looks like media queries are loaded in 1dppx context, it does not matter if you use "em", it seems they are cached on load and values such as min-width are fixed to the first value and eventhough you change the base font-size in different media query, other queries don't refresh.
The only solution I can think of right now is to multiply all media queries. One media query for px value in 1 dppx context and another media query for px value in 2 dppx context (which is 50% of the first value).

Thanks to Mr Lister in comments, I found out that this:
#media ( min-resolution: 2dppx) {
body, html {
font-size: 50%;
}
}
...is not what I intended to do. The 50% rule works for <html> tag AND for <body> tag. The resulting font size is not 8px in body, but 4px, because 50% in html is 8px and another 50% in nested body tag results in 4px.
What I intended to do was this:
#media ( min-resolution: 2dppx) {
html {
font-size: 50%;
}
}
Font-size issue is solved.
For the media query: I found out that sizes in #media queries ignore pixel density but sizes in the CSS rule body do not. Since I use "em" sizes and SASS, I can simply replace all #media queries with this mixin (example with 2dppx):
#mixin media-min-width($min-width) {
#media(min-width:$min-width), (min-width: $min-width / 2) and (min-resolution: 2dppx) {
#content;
}
}

Related

How to implement max-font-size?

I want to specify my font size using vw, as in
font-size: 3vw;
However, I also want to limit the font size to say 36px. How can I achieve the equivalent of max-font-size, which does not exist--is the only option to use media queries?
font-size: 3vw; means that the font size will be 3% of the viewport width. So when the viewport width is 1200px - the font size will be 3% * 1200px = 36px.
So a max-font-size of 36px can be easily implemented using a single media query to override the default 3vw font-size value.
Codepen demo (Resize Browser)
div {
font-size: 3vw;
}
#media screen and (min-width: 1200px) {
div {
font-size: 36px;
}
}
<div>hello</div>
Update: With the new CSS min() function, we can simplify the above code - without using media queries (caniuse)
div {
font-size: min(3vw, 36px);
}
In the above example, the font-size will be at most 36px, but will decrease to 3vw if the the viewport is less than 1200px wide (where 3vw computes to a value less than 36px )
That being said, using viewport units for font-size in the above way is problematic because when the viewport width is much smaller - say 320px - then the rendered font size will become 0.03 x 320 = 9.6px which is very (too) small.
In order to overcome this problem, I can recommend using a technique called Fluid Type AKA CSS Locks.
A CSS lock is a specific kind of CSS value calculation where:
there is a minimum value and a maximum value,
and two breakpoints (usually based on the viewport width),
and between those breakpoints, the actual value goes linearly from the minimum to the maximum.
So let's say we want to apply the above technique such that the minimum font-size is 16px at a viewport width of 600px or less, and will increase linearly until it reaches a maximum of 32px at a viewport width of 1200px.
This can be represented as follows (see this CSS-tricks article for more details):
div {
font-size: 16px;
}
#media screen and (min-width: 600px) {
div {
font-size: calc(16px + 16 * ((100vw - 600px) / 600));
}
}
#media screen and (min-width: 1200px) {
div {
font-size: 32px;
}
}
Alternatively, we could use this SASS mixin which does all of the math for us so that the CSS would look something like this:
/*
1) Set a min-font-size of 16px when viewport width < 600px
2) Set a max-font-size of 32px when viewport width > 1200px and
3) linearly increase the font-size from 16->32px
between a viewport width of 600px-> 1200px
*/
div {
#include fluid-type(font-size, 600px, 1200px, 16px, 32px);
}
// ----
// libsass (v3.3.6)
// ----
// =========================================================================
//
// PRECISE CONTROL OVER RESPONSIVE TYPOGRAPHY FOR SASS
// ---------------------------------------------------
// Indrek Paas #indrekpaas
//
// Inspired by Mike Riethmuller's Precise control over responsive typography
//
//
// `strip-unit()` function by Hugo Giraudel
//
// 11.08.2016 Remove redundant `&` self-reference
// 31.03.2016 Remove redundant parenthesis from output
// 02.10.2015 Add support for multiple properties
// 24.04.2015 Initial release
//
// =========================================================================
#function strip-unit($value) {
#return $value / ($value * 0 + 1);
}
#mixin fluid-type($properties, $min-vw, $max-vw, $min-value, $max-value) {
#each $property in $properties {
#{$property}: $min-value;
}
#media screen and (min-width: $min-vw) {
#each $property in $properties {
#{$property}: calc(#{$min-value} + #{strip-unit($max-value - $min-value)} * (100vw - #{$min-vw}) / #{strip-unit($max-vw - $min-vw)});
}
}
#media screen and (min-width: $max-vw) {
#each $property in $properties {
#{$property}: $max-value;
}
}
}
// Usage:
// ======
// /* Single property */
// html {
// #include fluid-type(font-size, 320px, 1366px, 14px, 18px);
// }
// /* Multiple properties with same values */
// h1 {
// #include fluid-type(padding-bottom padding-top, 20em, 70em, 2em, 4em);
// }
////////////////////////////////////////////////////////////////////////////
div {
#include fluid-type(font-size, 600px, 1200px, 16px, 32px);
}
#media screen and (max-width: 600px) {
div {
font-size: 16px;
}
}
#media screen and (min-width: 1200px) {
div {
font-size: 36px;
}
}
<div>Responsive Typography technique known as Fluid Type or CSS Locks.
Resize the browser window to see the effect.
</div>
Codepen Demo
Update: We can use the new clamp() CSS function (caniuse) to refactor the above code to simply:
div {
font-size: clamp(16px, 3vw, 32px);
}
see MDN:
clamp() allows you to set a font-size that grows with the size of the
viewport, but doesn't go below a minimum font-size or above a maximum
font-size. It has the same effect as the code in Fluid Typography but
in one line, and without the use of media queries.
p { font-size: clamp(1rem, 2.5vw, 1.5rem); }
<p>
If 2.5vw is less than 1rem, the font-size will be 1rem.
If 2.5vw is greater than 1.5rem, the font-size will be 1.5rem.
Otherwise, it will be 2.5vw.
</p>
--
Further Reading
Fluid Typography
How Do You Do max-font-size in CSS?
Fluid Responsive Typography With CSS Poly Fluid Sizing
Non-linear interpolation in CSS
Here is another idea. The calc function uses double precision float. Therefore it exhibits a step function near 1e18. For example,
width: calc(6e18px + 100vw - 6e18px);
This will snap to values 0px, 1024px, 2048px, etc. see pen https://codepen.io/jdhenckel/pen/bQNgyW
The step function can be used to create abs value and min/max with some clever maths. For instance
max(x, y) = x - (x + y) * step(y - x)
Given step(z) is zero when z<0 and one otherwise.
just an idea, not very practical, but maybe fun to try.
(Caution: this technique depends on an implementation detail that is not in any specification; currently, it works in Chrome and Safari, but not in Firefox, Edge or Internet Explorer, which don’t use double-precision floats for CSS values.)
UPDATE: this post is no longer useful (was it ever?) since CSS now supports min, max, and clamp.
Another way increases font size slowly, this will not limit max font size, but even on very wide screens, it will look better. Does not answer question in perfect way, but its 1 line...
font-size: calc(16px + 1vw);
Update: CSS improved and i recommend using clamp(min, preferred, max) function:
font-size: clamp(12px, 2vw, 20px);
At some point, the font-size exceeds the 36px scale right, find that. Assuming, it exceeds when the width: 2048px:
#media screen and (min-width: 2048px) {
.selector {
font-size: 36px;
}
}
Yes, unfortunately you need to use the #media queries. I hope that doesn't affect anything.
According to this website (there are ads in this site),
If you don't want to use clamp():
font-size: 24px;
font-size: min(max(3.5vw, 16px), 40px);
Line one for IE.
Line two for others, means font-size:3.5vw, max-font-size:40px, min-font-size:16px.

Pure CSS if/ else statement involving custom unit of size variables

I want to solve my issue only using pure CSS.
Here's the problematic code that I have:
#if (100vh > 830px) {:root { --cwv:100vh;}}
#else {:root { --cwv: 830px;}}
What I'm trying to do: if browser window size is smaller than 830px, the "- -cvw" (custom width variable/ unit) should be relative (another problem that my code probably has) to 830px rather than 100vw (built-in viewport "vertical width" unit), as the elements on the page get too small.
I know I can use multiple other ways to solve this problem using other languages, but I am just wondering how to make the CSS code work - as it is supposed to - according to the places that I have researched:
CSS custom properties (variables) - states that variables are declared in the following manner: :root{--variable-name: variable-value;}
CSS Conditionals (if/ else statements)
What am I doing wrong? I expect that I have multiple syntax errors in my code (that I've provided above) too... :/
Use media queries max-width.
Example:
Default --cwx set to viewport width:
:root{
--cwv: 100vw;
}
In case where viewport width is lesser or equal 830px, set --cwx to 830px:
#media all and (max-width: 830px){
:root{
--cwv: 830px;
}
}
Finally, example's black bar will be set to ½ of --cwx, thus ½ of viewport width, but not less than ½ of 830px (=415px):
:root{
--cwv: 100vw;
}
#media all and (max-width: 830px){
:root{
--cwv: 830px;
}
}
body{
margin: 0;
}
:root::after{
content: "";
display: block;
background: black;
height: 10px;
width: calc(var(--cwv) * .5);
}
use media queries for window size like this below
#media screen and (min-width : 830 px) {
/* your code */
}

SASS: Make email mobile font resizing more efficient

I am making a responsive HTML email. When I open it on a mobile device (e.g. iPhone), the layout is responsive, but the fonts are tiny.
The only solution seems to be redeclare the fonts in a media query at a bigger size. Getting the right size takes a lot of trial and error.
Obviously, having two sets of font declarations is inefficient to maintain so I want to use SCSS to streamline it.
This is what I have at the moment:
h1 {
font-size: 28px;
line-height: 36px;
}
h2 {
font-size: 14px;
line-height: 18px;
}
#media only screen and (max-device-width: 615px) {
$increase: 8px;
h1 {
font-size: 28px + $increase;
line-height: 36px + $increase;
}
h2 {
font-size: 14px + $increase;
line-height: 18px + $increase;
}
}
This is good as I can just alter the $increase value to make my mobile fonts bigger. However, I have over 20 font declarations (for different emails), so if I update the desktop sizes (e.g. change h1 from 28px to 32px), I then have to update mobile declaration, which is time consuming.
Is there any way I can use SASS to have one set of font declarations and then automatically have the mobile versions increase in size (while still having the flexibility to do some custom overrides if the $increase value isn't suitable for a particular style).
Steps I have tried to overcome the problem:
1. Using Rem/Ems:
These don't seem to be supported by all Desktop browsers. Using PX seems to be the only way to get the size right.
2. Using Scale meta tag:
e.g. <meta name="viewport" content="width=device-width">
This causes some mobile browsers to display a white screen (Blackberry)
You can use rems! Use the font-size:62.5%; trick on your html element first, and then you can set up several media queries just to resize the rems.
#media only screen and (min-width: 385px) {
html{font-size:68%;}
}
#media only screen and (max-width: 370px) {
html{font-size:62.5%;}
}
#media only screen and (max-width: 350px) {
html{font-size:61%;}
}
#media only screen and (max-width: 330px) {
html{font-size:59%;}
}
And for the desktop clients that don't support rems you can just put your px definitions first in the inline css (or style tag):
font-size:14px;
line-height:16px;
font-size:1.4rem;
line-height:1.6rem;
I'm currently working on a way to get SASS mixin to copy the px values and convert them to rems, but it's tricky because of the decimal point. If i finish i'll post a comment! Or if you beat me to it please let me know ;)
The only thing you can really do is use extends, and I caution you to use them sparingly as they can really bulk up your CSS:
%size-1 {
font-size: 1.1em;
}
%size-2 {
font-size: 1em;
}
#media (min-width: 30em) {
%size-1 {
font-size: 1.2em;
}
%size-2 {
font-size: 1.1em;
}
}
h1 {
#extend %size-1;
}
h2 {
#extend %size-2;
}
You should not need to modify your line-height every time you change the font-size if you specify it without units (eg. line-height: 1.5).

Why do I have to put media queries at the bottom of the stylesheet?

I am new to learning responsive design. What I have noticed on my journey is that when I put media queries at the bottom of the stylesheet, everything works flawlessly in regards to breakpoints. If I put the media queries at the top of the stylesheet, nothing works, and only recently I found out that I need to add !important and max-DEVICE-width ( as opposed to max-width) to the css that is being changed.
Why is this? Why do the media queries work on both desktop and mobile when put at the bottom of the stylesheet.
Why is it that when I put media queries on the top of the stylesheet I need to add !important and also max-DEVICE-width in order for the breakpoints to work on desktop and mobile?
Because css is read from top to bottom. The rule that is set last, is the one that will be executed.
Translating, it is like this:
#media (max-width: 600px) { //If my screen fits this size
.text {
color: red; //Paint it red
}
}
.text {
color: yellow; //Now, forget about everything and paint it yellow!
}
When you add !important is like saying:
#media (max-width: 600px) { //If my screen fits this size
.text {
color: red !important; //Paint it red, and don't change it ever!!!
}
}
.text {
color: yellow; //Ok, I'm not going to paint it yellow....
}
CSS is read from top to bottom.
Everything that is below some other css will overwrite what's on top of it.
It is possible however to use !important at the end of a CSS parameter to make it overwrite everything else
body{
background-color: black !important;
}
body{
background-color: pink;
}
The background-color will be black.
If you remove the !important, it will be pink.
Media queries cascade with the rest of the stylesheet. You can intersperse media queries within your stylesheet, and so you can also cascade styles as needed.
For example:
.my-class {
color: red;
}
.my-class--modifier {
color: blue;
}
#media screen and (min-width: 760px) {
.my-class--modifier {
color: green;
}
}
.some-other-class {
width: 200px;
}
#media screen and (min-width: 760px) {
.some-other-class {
width: 700px;
background-color: gray;
}
.some-other-class .my-class {
border: 2px solid red;
border-radius: 4pt;
}
}
This works precisely due to CSS's cascading nature. You can organize media queries as required based on sections, individual selectors and more.
Basically you are using media queries when you want to apply CSS styles depending on a device's general type (such as print vs. screen), specific characteristics (such as the width of the browser viewport, or environment (such as ambient light conditions).
When you started designing, you generally started doing it for one device of known specifications. So you design it according to you current device and then apply it for other screen sizes.
Hence the order goes like this: Make complete design --> Add the media query to fit for desired screen sizes at the bottom.
It is preferrable to write the query at the bottom became of precedence. That will save you from stress of using important! everytime.

How can I avoid media query overlap?

The cascade is what makes CSS special and powerful. But in the case of media queries, overlap can seem problematic.
Consider the following CSS (continuing rules for CSS media query overlap):
/* Standard - for all screens below 20em */
body { color: black; font-size: 1em; }
/* Query A - slightly wider, mobile viewport */
#media (min-width: 20em) and (max-width: 45em) {
body { color: red; } /* supposed to be unique for this width */
}
/* Query B - everything else */
#media (min-width: 45em) {
body { font-size: larger; } /* because viewport is bigger */
}
So when the screen is exactly 45em wide, the overlap at 45em will be treated according to the standard CSS cascade:
All max-width: 45em definitions will be applied first,
and all min-width: 45em will be applied thereafter.
Consider these two conditions:
All text would normally be black, but Query A is unique and has color: red.
Since Query B is for larger viewports, it's text has the CSS font-size: larger.
Therefore, at a width of exactly 45em, we'd get big and red text. What would be the best solution to avoid this?
I see two possibilities:
Re-declare the text to have color: black in Query B, but then you're managing two declarations if you choose to change the color in the future. (Of course, not such a problem with this single line of code, but imagine there's a lot of other declarations and selectors.)
Avoid overlap by using pixel values like max-width: 799px and min-width: 800px, but then you're using pixels — I guess they could be 49.9375em and 50em, respectively. Though what if the default is no longer 16em and something gets rounded? And we're still not certain what happens at that gap. (A black hole that breaks the space-time continuum?)
Both have their drawbacks... any other ideas?
The only reliable way to create two mutually exclusive #media blocks for any given media query is to use not to negate it in one of the blocks. Unfortunately, this means repeating your media query once for each #media block. So, instead of this for example:
#media (max-width: 49.9375em) {
body {
color: red;
}
}
#media (min-width: 50em) {
body {
font-size: larger;
}
}
You would have this:
/*
* Note: Media Queries 4 still requires 'not' to be followed by a
* media type (e.g. 'all' or 'screen') for reasons I cannot comprehend.
*/
#media not all and (min-width: 50em) {
body {
color: red;
}
}
#media (min-width: 50em) {
body {
font-size: larger;
}
}
Interactive jsFiddle demo
This is very effective at closing the gap with range media features like width and height since it essentially turns this into an either-or scenario. But, like your first two options, it isn't perfect: as mentioned, you have to repeat the same media query twice, and add not to one of them. There is no if/else construct for #media as described in Conditional Rules 3.
Although I mention this in my answer to your previous question:
From my experiments it would seem Safari on iOS rounds all fractional pixel values to ensure that either one of max-width: 799px and min-width: 800px will match, even if the viewport is really 799.5px (which apparently matches the former).
It should be noted, still, that I've noticed some quirks when it comes to rounding. That said, I haven't been able to find a fractional value that would evade both media queries and end up not receiving styles from either set of rules (which, by the way, is the worst that can happen, so don't worry about potentially creating a space-time rift). That must mean browsers — at least, Safari as I've tested — do a reasonable job of ensuring they satisfy media queries even if you have values that differ (by exactly 1 CSS pixel).
When it comes to units with larger gaps that can be observed on desktop browsers, though, like ems, there is a much larger margin of error. For example, one comment suggests using 49.99999em instead of something more arbitrary than 49.9375em, but apparently there is a difference, at least with a default font size of 16px.
I simplified your code, changed the media queries to use decimal values, and put the code in jsFiddle:
#media (max-width: 49.9375em) {
body {
color: red;
}
}
#media (min-width: 50em) {
body {
font-size: larger;
}
}
If you resize the Result pane to exactly 800 pixels (the text will update to guide you along), you actually end up with different results depending on whether #media (max-width: 49.9375em) is used, or #media (max-width: 49.99999em) is used (I was surprised by this too)...
Either way, you're right: option 2 has its drawbacks too. I'm not particularly fond of it, to be honest, because I wouldn't want to crack my head over device and user agent quirks which are out of my control. If you're like me, I suppose it would be better to go through the inconvenience of redeclaring your rules at the cost (?) of being more vigilant around your code, as that's at least still within your control as an author.
For me, the best way is to keep a gap of 0.01em:
#media (min-width: 20em) and (max-width: 44.99em) {
body { color: red; } /* supposed to be unique for this width */
}
#media (min-width: 45em) {
body { font-size: larger; } /* because viewport is bigger */
}
I recommend you to read this article for the details and the comparison of the different solutions to prevent media query overlapping.
Cheers,
Thomas.

Resources