Close Menu
    DevStackTipsDevStackTips
    • Home
    • News & Updates
      1. Tech & Work
      2. View All

      Sunshine And March Vibes (2025 Wallpapers Edition)

      June 1, 2025

      The Case For Minimal WordPress Setups: A Contrarian View On Theme Frameworks

      June 1, 2025

      How To Fix Largest Contentful Paint Issues With Subpart Analysis

      June 1, 2025

      How To Prevent WordPress SQL Injection Attacks

      June 1, 2025

      My top 5 must-play PC games for the second half of 2025 — Will they live up to the hype?

      June 1, 2025

      A week of hell with my Windows 11 PC really makes me appreciate the simplicity of Google’s Chromebook laptops

      June 1, 2025

      Elden Ring Nightreign Night Aspect: How to beat Heolstor the Nightlord, the final boss

      June 1, 2025

      New Xbox games launching this week, from June 2 through June 8 — Zenless Zone Zero finally comes to Xbox

      June 1, 2025
    • Development
      1. Algorithms & Data Structures
      2. Artificial Intelligence
      3. Back-End Development
      4. Databases
      5. Front-End Development
      6. Libraries & Frameworks
      7. Machine Learning
      8. Security
      9. Software Engineering
      10. Tools & IDEs
      11. Web Design
      12. Web Development
      13. Web Security
      14. Programming Languages
        • PHP
        • JavaScript
      Featured

      Student Record Android App using SQLite

      June 1, 2025
      Recent

      Student Record Android App using SQLite

      June 1, 2025

      When Array uses less memory than Uint8Array (in V8)

      June 1, 2025

      Laravel 12 Starter Kits: Definite Guide Which to Choose

      June 1, 2025
    • Operating Systems
      1. Windows
      2. Linux
      3. macOS
      Featured

      My top 5 must-play PC games for the second half of 2025 — Will they live up to the hype?

      June 1, 2025
      Recent

      My top 5 must-play PC games for the second half of 2025 — Will they live up to the hype?

      June 1, 2025

      A week of hell with my Windows 11 PC really makes me appreciate the simplicity of Google’s Chromebook laptops

      June 1, 2025

      Elden Ring Nightreign Night Aspect: How to beat Heolstor the Nightlord, the final boss

      June 1, 2025
    • Learning Resources
      • Books
      • Cheatsheets
      • Tutorials & Guides
    Home»News & Updates»Typecasting and Viewport Transitions in CSS With tan(atan2())

    Typecasting and Viewport Transitions in CSS With tan(atan2())

    February 12, 2025

    We’ve been able to get the length of the viewport in CSS since… checks notes… 2013! Surprisingly, that was more than a decade ago. Getting the viewport width is as easy these days as easy as writing 100vw, but what does that translate to, say, in pixels? What about the other properties, like those that take a percentage, an angle, or an integer?

    Think about changing an element’s opacity, rotating it, or setting an animation progress based on the screen size. We would first need the viewport as an integer — which isn’t currently possible in CSS, right?

    What I am about to say isn’t a groundbreaking discovery, it was first described amazingly by Jane Ori in 2023. In short, we can use a weird hack (or feature) involving the tan() and atan2() trigonometric functions to typecast a length (such as the viewport) to an integer. This opens many new layout possibilities, but my first experience was while writing an Almanac entry in which I just wanted to make an image’s opacity responsive.

    Resize the CodePen and the image will get more transparent as the screen size gets smaller, of course with some boundaries, so it doesn’t become invisible:

    CodePen Embed Fallback

    This is the simplest we can do, but there is a lot more. Take, for example, this demo I did trying to combine many viewport-related effects. Resize the demo and the page feels alive: objects move, the background changes and the text smoothly wraps in place.

    CodePen Embed Fallback

    I think it’s really cool, but I am no designer, so that’s the best my brain could come up with. Still, it may be too much for an introduction to this typecasting hack, so as a middle-ground, I’ll focus only on the title transition to showcase how all of it works:

    CodePen Embed Fallback

    Setting things up

    The idea behind this is to convert 100vw to radians (a way to write angles) using atan2(), and then back to its original value using tan(), with the perk of coming out as an integer. It should be achieved like this:

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

    But! Browsers aren’t too keep on this method, so a lot more wrapping is needed to make it work across all browsers. The following may seem like magic (or nonsense), so I recommend reading Jane’s post to better understand it, but this way it will work in all browsers:

    @property --100vw {
      syntax: "<length>";
      initial-value: 0px;
      inherits: false;
    }
    
    :root {
      --100vw: 100vw;
      --int-width: calc(10000 * tan(atan2(var(--100vw), 10000px)));
    }

    Don’t worry too much about it. What’s important is our precious --int-width variable, which holds the viewport size as an integer!

    CodePen Embed Fallback

    Wideness: One number to rule them all

    Right now we have the viewport as an integer, but that’s just the first step. That integer isn’t super useful by itself. We oughta convert it to something else next since:

    • different properties have different units, and
    • we want each property to go from a start value to an end value.

    Think about an image’s opacity going from 0 to 1, an object rotating from 0deg to 360deg, or an element’s offset-distance going from 0% to 100%. We want to interpolate between these values as --int-width gets bigger, but right now it’s just an integer that usually ranges between 0 to 1600, which is inflexible and can’t be easily converted to any of the end values.

    The best solution is to turn --int-width into a number that goes from 0 to 1. So, as the screen gets bigger, we can multiply it by the desired end value. Lacking a better name, I call this “0-to-1” value --wideness. If we have --wideness, all the last examples become possible:

    /* If `--wideness is 0.5 */
    
    .element {
      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 value between 0 to 1 that represents how wide the screen is: 0 represents when the screen is narrow, and 1 represents when it’s wide. But we still have to set what those values mean in the viewport. For example, we may want 0 to be 400px and 1 to be 1200px, our viewport transitions will run between these values. Anything below and above is clamped to 0 and 1, respectively.

    Animation Zone between 400px and 1200px

    In CSS, we can write that as follows:

    :root {
      /* Both 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))
      );
    }

    Besides easy conversions, the --wideness variable lets us define the lower and upper limits in which the transition should run. And what’s even better, we can set the transition zone at a middle spot so that the user can see it in its full glory. Otherwise, the screen would need to be 0px so that --wideness reaches 0 and who knows how wide to reach 1.

    CodePen Embed Fallback

    We got the --wideness. What’s next?

    For starters, the title’s markup is divided into spans since there is no CSS-way to select specific words in a sentence:

    <h1><span>Resize</span> and <span>enjoy!</span></h1>

    And since we will be doing the line wrapping ourselves, it’s important to unset some defaults:

    h1 {
      position: absolute; /* Keeps the text at the center */
      white-space: nowrap; /* Disables line wrapping */
    }

    The transition should work without the base styling, but it’s just too plain-looking. They are below if you want to copy them onto your stylesheet:

    CodePen Embed Fallback

    And just as a recap, our current hack looks like this:

    @property --100vw {
      syntax: "<length>";
      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, enough with the set-up. It’s time to use our new values and make the viewport transition. We first gotta identify how the title should be rearranged for smaller screens: as you saw in the initial demo, the first span goes up and right, while the second span does the opposite and goes down and left. So, the end position for both spans translates to the following values:

    h1 {
      span:nth-child(1) {
        display: inline-block; /* So transformations work */
        position: relative;
        bottom: 1.2lh;
        left: 50%;
        transform: translate(-50%);
      }
    
      span:nth-child(2) {
        display: inline-block; /* So transformations work */
        position: relative;
        bottom: -1.2lh;
        left: -50%;
        transform: translate(50%);
      }
    }

    Before going forward, both formulas are basically the same, but with different signs. We can rewrite them at once bringing one new variable: --direction. It will be either 1 or -1 and define which direction to run the transition:

    h1 {
      span {
        display: inline-block;
        position: relative;
        bottom: calc(1.2lh * var(--direction));
        left: calc(50% * var(--direction));
        transform: translate(calc(-50% * var(--direction)));
        }
    
      span:nth-child(1) {
        --direction: 1;
      }
    
      span:nth-child(2) {
        --direction: -1;
      }
    }
    
    CodePen Embed Fallback

    The next step would be bringing --wideness into the formula so that the values change as the screen resizes. However, we can’t just multiply everything by --wideness. Why? Let’s see what happens if we do:

    span {
      display: inline-block;
      position: relative;
      bottom: calc(var(--wideness) * 1.2lh * var(--direction));
      left: calc(var(--wideness) * 50% * var(--direction));
      transform: translate(calc(var(--wideness) * -50% * var(--direction)));
    }

    As you’ll see, everything is backwards! The words wrap when the screen is too wide, and unwrap when the screen is too narrow:

    CodePen Embed Fallback

    Unlike our first examples, in which the transition ends as --wideness increases from 0 to 1, we want to complete the transition as --wideness decreases from 1 to 0, i.e. while the screen gets smaller the properties need to reach their end value. This isn’t a big deal, as we can rewrite our formula as a subtraction, in which the subtracting number gets bigger as --wideness increases:

    span {
      display: inline-block;
      position: relative;
      bottom: calc((1.2lh - var(--wideness) * 1.2lh) * var(--direction));
      left: calc((50% - var(--wideness) * 50%) * var(--direction));
      transform: translate(calc((-50% - var(--wideness) * -50%) * var(--direction)));
    }

    And now everything moves in the right direction while resizing the screen!

    CodePen Embed Fallback

    However, you will notice how words move in a straight line and some words overlap while resizing. We can’t allow this since a user with a specific screen size may get stuck at that point in the transition. Viewport transitions are cool, but not at the expense of ruining the experience for certain screen sizes.

    Instead of moving in a straight line, words should move in a curve such that they pass around the central word. Don’t worry, making a curve here is easier than it looks: just move the spans twice as fast in the x-axis as they do in the y-axis. This can be achieved by multiplying --wideness by 2, although we have to cap it at 1 so it doesn’t overshoot past the final value.

    span {
     display: inline-block;
     position: relative;
     bottom: calc((1.2lh - var(--wideness) * 1.2lh) * var(--direction));
     left: calc((50% - min(var(--wideness) * 2, 1) * 50%) * var(--direction));
     transform: translate(calc((-50% - min(var(--wideness) * 2, 1) * -50%) * var(--direction)));
    }

    Look at that beautiful curve, just avoiding the central text:

    CodePen Embed Fallback

    This is just the beginning!

    It’s surprising how powerful having the viewport as an integer can be, and what’s even crazier, the last example is one of the most basic transitions you could make with this typecasting hack. Once you do the initial setup, I can imagine a lot more possible transitions, and --widenesss is so useful, it’s like having a new CSS feature right now.

    I expect to see more about “Viewport Transitions” in the future because they do make websites feel more “alive” than adaptive.


    Typecasting and Viewport Transitions in CSS With tan(atan2()) originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

    Source: Read More 

    Hostinger
    Facebook Twitter Reddit Email Copy Link
    Previous ArticleIl Progetto GNOME Lancia un Sito Web Rivisitato: Minimalismo e Nuove Funzionalità
    Next Article SonicRadio – stylish TUI radio player

    Related Posts

    News & Updates

    My top 5 must-play PC games for the second half of 2025 — Will they live up to the hype?

    June 1, 2025
    News & Updates

    A week of hell with my Windows 11 PC really makes me appreciate the simplicity of Google’s Chromebook laptops

    June 1, 2025
    Leave A Reply Cancel Reply

    Continue Reading

    PC shipments surge as Windows 11 adoption rises, but the trade war threatens momentum

    News & Updates

    Russian bots hard at work spreading political unrest on Romania’s internet

    Development

    Designing a new way to optimize complex coordinated systems

    Artificial Intelligence

    CVE-2025-47490 – Rustaurius Ultimate WP Mail SQL Injection Vulnerability

    Common Vulnerabilities and Exposures (CVEs)

    Highlights

    Build a Bootstrap light/dark toggle switch component

    February 21, 2025

    In this tutorial, we’ll extend Bootstrap by building a custom color mode switcher that takes…

    Scale your relational database for SaaS, Part 2: Sharding and routing

    April 30, 2024

    Ivanti Rushes Patches for 4 New Flaw in Connect Secure and Policy Secure

    April 4, 2024

    Exploring Data Mapping as a Search Problem

    May 18, 2024
    © DevStackTips 2025. All rights reserved.
    • Contact
    • Privacy Policy

    Type above and press Enter to search. Press Esc to cancel.