Wednesday, February 12, 2025
HomeProgrammingTypecasting And Viewport Transitions In CSS With Tan(atan2())

Typecasting And Viewport Transitions In CSS With Tan(atan2())


We’ve been capable of get the size of the viewport in CSS since… checks notes… 2013! Surprisingly, that was greater than a decade in the past. Getting the viewport width is as simple nowadays as simple as writing 100vw, however what does that translate to, say, in pixels? What concerning the different properties, like people who take a proportion, an angle, or an integer?

Take into consideration altering a component’s opacity, rotating it, or setting an animation progress primarily based on the display screen dimension. We might first want the viewport as an integer — which isn’t at the moment doable in CSS, proper?

What I’m about to say isn’t a groundbreaking discovery, it was first described amazingly by Jane Ori in 2023. In brief, we will use a bizarre hack (or function) involving the tan() and atan2() trigonometric capabilities to typecast a size (such because the viewport) to an integer. This opens many new format prospects, however my first expertise was whereas writing an Almanac entry through which I simply wished to make a picture’s opacity responsive.

Resize the CodePen and the picture will get extra clear because the display screen dimension will get smaller, in fact with some boundaries, so it doesn’t turn out to be invisible:

That is the best we will do, however there may be much more. Take, for instance, this demo I did making an attempt to mix many viewport-related results. Resize the demo and the web page feels alive: objects transfer, the background modifications and the textual content easily wraps in place.

I believe it’s actually cool, however I’m no designer, in order that’s the very best my mind might provide you with. Nonetheless, it could be an excessive amount of for an introduction to this typecasting hack, in order a middle-ground, I’ll focus solely on the title transition to showcase how all of it really works:

Setting issues up

The thought behind that is to transform 100vw to radians (a approach to write angles) utilizing atan2(), after which again to its authentic worth utilizing tan(), with the perk of popping out as an integer. It must be achieved like this:

:root {
  --int-width: tan(atan2(100vw, 1px));
}

However! Browsers aren’t too carry on this methodology, so much more wrapping is required to make it work throughout all browsers. The next might seem to be magic (or nonsense), so I like to recommend studying Jane’s publish to raised perceive it, however this fashion it would work in all browsers:

@property --100vw {
  syntax: "<size>";
  initial-value: 0px;
  inherits: false;
}

:root {
  --100vw: 100vw;
  --int-width: calc(10000 * tan(atan2(var(--100vw), 10000px)));
}

Don’t fear an excessive amount of about it. What’s necessary is our valuable --int-width variable, which holds the viewport dimension as an integer!

Wideness: One quantity to rule all of them

Proper now we now have the viewport as an integer, however that’s simply step one. That integer isn’t tremendous helpful by itself. We oughta convert it to one thing else subsequent since:

  • completely different properties have completely different items, and
  • we wish every property to go from a begin worth to an finish worth.

Take into consideration a picture’s opacity going from 0 to 1, an object rotating from 0deg to 360deg, or a component’s offset-distance going from 0% to 100%. We wish to interpolate between these values as --int-width will get greater, however proper now it’s simply an integer that often ranges between 0 to 1600, which is rigid and might’t be simply transformed to any of the top values.

One of the best answer is to show --int-width right into a quantity that goes from 0 to 1. So, because the display screen will get greater, we will multiply it by the specified finish worth. Missing a greater title, I name this “0-to-1” worth --wideness. If we now have --wideness, all of the final examples turn out to be doable:

/* If `--wideness is 0.5 */

.ingredient {
  opacity: var(--wideness); /* is 0.5 */
  translate: rotate(calc(wideness(400px, 1200px) * 360deg)); /* is 180deg */
  offset-distance: calc(var(--wideness) * 100%); /* is 50% */
}

So --wideness is a price between 0 to 1 that represents how extensive the display screen is: 0 represents when the display screen is slender, and 1 represents when it’s extensive. However we nonetheless need to set what these values imply within the viewport. For instance, we might want 0 to be 400px and 1 to be 1200px, our viewport transitions will run between these values. Something under and above is clamped to 0 and 1, respectively.

Animation Zone between 400px and 1200px

In CSS, we will write that as follows:

:root {
  /* Each bounds are unitless */
  --lower-bound: 400; 
  --upper-bound: 1200;

  --wideness: calc(
    (clamp(var(--lower-bound), var(--int-width), var(--upper-bound)) - var(--lower-bound)) / (var(--upper-bound) - var(--lower-bound))
  );
}

Apart from simple conversions, the --wideness variable lets us outline the decrease and higher limits through which the transition ought to run. And what’s even higher, we will set the transition zone at a center spot in order that the person can see it in its full glory. In any other case, the display screen would must be 0px in order that --wideness reaches 0 and who is aware of how extensive to achieve 1.

We acquired the --wideness. What’s subsequent?

For starters, the title’s markup is split into spans since there isn’t a CSS-way to pick particular phrases in a sentence:

<h1><span>Resize</span> and <span>get pleasure from!</span></h1>

And since we might be doing the road wrapping ourselves, it’s necessary to unset some defaults:

h1 {
  place: absolute; /* Retains the textual content on the heart */
  white-space: nowrap; /* Disables line wrapping */
}

The transition ought to work with out the bottom styling, but it surely’s simply too plain-looking. They’re under if you wish to copy them onto your stylesheet:

And simply as a recap, our present hack seems like this:

@property --100vw {
  syntax: "<size>";
  initial-value: 0px;
  inherits: false;
}

:root {
  --100vw: 100vw;
  --int-width: calc(10000 * tan(atan2(var(--100vw), 10000px)));
  --lower-bound: 400;
  --upper-bound: 1200;

  --wideness: calc(
    (clamp(var(--lower-bound), var(--int-width), var(--upper-bound)) - var(--lower-bound)) / (var(--upper-bound) - var(--lower-bound))
  );
}

OK, sufficient with the set-up. It’s time to make use of our new values and make the viewport transition. We first gotta establish how the title must be rearranged for smaller screens: as you noticed within the preliminary demo, the primary span goes up and proper, whereas the second span does the other and goes down and left. So, the top place for each spans interprets to the next values:

h1 {
  span:nth-child(1) {
    show: inline-block; /* So transformations work */
    place: relative;
    backside: 1.2lh;
    left: 50%;
    remodel: translate(-50%);
  }

  span:nth-child(2) {
    show: inline-block; /* So transformations work */
    place: relative;
    backside: -1.2lh;
    left: -50%;
    remodel: translate(50%);
  }
}

Earlier than going ahead, each formulation are mainly the identical, however with completely different indicators. We are able to rewrite them directly bringing one new variable: --direction. It is going to be both 1 or -1 and outline which path to run the transition:

h1 {
  span {
    show: inline-block;
    place: relative;
    backside: calc(1.2lh * var(--direction));
    left: calc(50% * var(--direction));
    remodel: translate(calc(-50% * var(--direction)));
    }

  span:nth-child(1) {
    --direction: 1;
  }

  span:nth-child(2) {
    --direction: -1;
  }
}

The subsequent step could be bringing --wideness into the components in order that the values change because the display screen resizes. Nonetheless, we will’t simply multiply every little thing by --wideness. Why? Let’s see what occurs if we do:

span {
  show: inline-block;
  place: relative;
  backside: calc(var(--wideness) * 1.2lh * var(--direction));
  left: calc(var(--wideness) * 50% * var(--direction));
  remodel: translate(calc(var(--wideness) * -50% * var(--direction)));
}

As you’ll see, every little thing is backwards! The phrases wrap when the display screen is simply too extensive, and unwrap when the display screen is simply too slender:

Not like our first examples, through which the transition ends as --wideness will increase from 0 to 1, we wish to full the transition as --wideness decreases from 1 to 0, i.e. whereas the display screen will get smaller the properties want to achieve their finish worth. This isn’t an enormous deal, as we will rewrite our components as a subtraction, through which the subtracting quantity will get greater as --wideness will increase:

span {
  show: inline-block;
  place: relative;
  backside: calc((1.2lh - var(--wideness) * 1.2lh) * var(--direction));
  left: calc((50% - var(--wideness) * 50%) * var(--direction));
  remodel: translate(calc((-50% - var(--wideness) * -50%) * var(--direction)));
}

And now every little thing strikes in the precise path whereas resizing the display screen!

Nonetheless, you’ll discover how phrases transfer in a straight line and a few phrases overlap whereas resizing. We are able to’t enable this since a person with a particular display screen dimension might get caught at that time within the transition. Viewport transitions are cool, however not on the expense of ruining the expertise for sure display screen sizes.

As a substitute of transferring in a straight line, phrases ought to transfer in a curve such that they go across the central phrase. Don’t fear, making a curve right here is less complicated than it seems: simply transfer the spans twice as quick within the x-axis as they do within the y-axis. This may be achieved by multiplying --wideness by 2, though we now have to cap it at 1 so it doesn’t overshoot previous the ultimate worth.

span {
 show: inline-block;
 place: relative;
 backside: calc((1.2lh - var(--wideness) * 1.2lh) * var(--direction));
 left: calc((50% - min(var(--wideness) * 2, 1) * 50%) * var(--direction));
 remodel: translate(calc((-50% - min(var(--wideness) * 2, 1) * -50%) * var(--direction)));
}

Have a look at that lovely curve, simply avoiding the central textual content:

That is just the start!

It’s shocking how highly effective having the viewport as an integer may be, and what’s even crazier, the final instance is likely one of the most elementary transitions you could possibly make with this typecasting hack. When you do the preliminary setup, I can think about much more doable transitions, and --widenesss is so helpful, it’s like having a brand new CSS function proper now.

I count on to see extra about “Viewport Transitions” sooner or later as a result of they do make web sites really feel extra “alive” than adaptive.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments