About

Saturday, December 17, 2022

CSS Infinite 3D Sliders

In this series, we’ve been making image sliders with nothing but HTML and CSS. The idea is that we can use the same markup but different CSS to get wildly different results, no matter how many images we toss in. We started with a circular slider that rotates infinitely, sort of like a fidget spinner that holds images. Then we made one that flips through a stack of photos.

This time around, we’re diving into the third dimension. It’s going to look tough at first, but lots of the code we’re looking at is exactly what we used in the first two articles in this series, with some modifications. So, if you’re just now getting into the series, I’d suggest checking out the others for context on the concepts we’re using here.

CSS Sliders series

This is what we’re aiming for:

At first glance, it looks like we have a rotating cube with four images. But in reality, we’re dealing with six images in total. Here is the slider from a different angle:

Now that we have a good visual for how the images are arranged, let’s dissect the code to see how we get there.

The basic setup

Same HTML as the rest of the sliders we’ve used for the other sliders:

<div class="gallery">
  <img src="" alt="">
  <img src="" alt="">
  <img src="" alt="">
  <img src="" alt="">
  <img src="" alt="">
</div>

And once again, we’re using CSS Grid to place the images in a stack, one on top of another:

.gallery {
  display: grid;
}
.gallery > img {
  grid-area: 1 / 1;
  width: 160px;
  aspect-ratio: 1;
  object-fit: cover;
}

The animation

The logic for this slider is very similar to the circular slider from the first article. In fact, if you check the video above again, you can see that the images are placed in a way that creates a polygon. After a full rotation, it returns to the first image.

We relied on the CSS transform-origin and animation-delay properties for that first slider. The same animation is applied to all of the image elements, which rotate around the same point. Then, by using different delays, we correctly place all the images around a big circle.

The implementation will be a bit different for our 3D slider. Using transform-origin won’t work here because we’re working in 3D, so we will use transform instead to correctly place all the images, then rotate the container.

We’re reaching for Sass again so we can loop through the number of images and apply our transforms:

@for $i from 1 to ($n + 1) {
  .gallery > img:nth-child(#{$i}) {
     transform: 
       rotate(#{360*($i - 1) / $n}deg) /* 1 */
       translateY(50% / math.tan(180deg / $n)) /* 2 */ 
       rotateX(90deg); /* 3 */
  }
}

You might be wondering why we’re jumping straight into Sass. We started with a fixed number of images using vanilla CSS in the other articles before generalizing the code with Sass to account for any number (N) of images. Well, I think you get the idea now and we can cut out all that discovery work to get to the real implementation.

The transform property is taking three values, which I’ve illustrated here:

Showing the three phases of the image slider layout.

We first rotate all the images above each other. The angle of rotation depends on the number of images. For N images, we have an increment equal to 360deg/N. Then we translate all of the images by the same amount in a way that makes their center points meet on the sides.

Showing the stack of images arranged flat in a circle with a red line running through the center point of the images.

There’s some boring geometry that helps explain how all this works, but the distance is equal to 50%/tan(180deg/N). We dealt with a similar equation when making the circular slider ( transform-origin: 50% 50%/sin(180deg/N) ).

Finally, we rotate the images around the x-axis by 90deg to get the arrangement we want. Here is a video that illustrates what the last rotation is doing:

Now all we have to do is to rotate the whole container to create our infinite slider.

.gallery {
  transform-style: preserve-3d;
  --_t: perspective(280px) rotateX(-90deg);
  animation: r 12s cubic-bezier(.5, -0.2, .5, 1.2) infinite;
}
@keyframes r {
  0%, 3% {transform: var(--_t) rotate(0deg); }
  @for $i from 1 to $n {
    #{($i/$n)*100 - 2}%, 
    #{($i/$n)*100 + 3}% {
      transform: var(--_t) rotate(#{($i / $n) * -360}deg);
    }  
  }
  98%, 100% { transform: var(--_t) rotate(-360deg); }
}

That code might be hard to understand, so let’s actually step back a moment and revisit the animation we made for the circular slider. This is what we wrote in that first article:

.gallery {
  animation: m 12s cubic-bezier(.5, -0.2, .5, 1.2) infinite;
}
@keyframes m {
  0%, 3% { transform: rotate(0); }
  @for $i from 1 to $n {
    #{($i / $n) * 100 - 2}%,
    #{($i / $n) * 100 + 3}% { 
      transform: rotate(#{($i / $n) * -360}deg);
    }  
  }
  98%, 100% { transform: rotate(-360deg); }
}

The keyframes are almost identical. We have the same percentage values, the same loop, and the same rotation.

Why are both the same? Because their logic is the same. In both cases, the images are arranged around a circular shape and we need to rotate the whole thing to show each image. That’s how I was able to copy the keyframes from the circular slider and use that same code for our 3D slider. The only difference is that we need to rotate the container by -90deg along the x-axis to see the images since we have already rotated them by 90deg on the same axis. Then we add a touch of perspective to get the 3D effect.

That’s it! Our slider is done. Here is the full demo again. All you have to do is to add as many images as you want and update one variable to get it going.

Vertical 3D slider

Since we are playing in the 3D space, why not make a vertical version of the previous slider? The last one rotates along the z-axis, but we can also move along the x-axis if we want.

If you compare the code for both versions of this slider, you might not immediately spot the difference because it’s only one character! I replaced rotate() with rotateX() inside the keyframes and the image transform. That’s it!

It should be noted that rotate() is equivalent to rotateZ(), so by changing the axis from Z to X we transform the slider from the horizontal version into the vertical one.

Cube slider

We cannot talk about 3D in CSS without talking about cubes. And yes, that means we are going to make another version of the slider.

The idea behind this version of the slider is to create an actual cube shape with the images and rotate the full thing in around the different axis. Since it’s a cube, we’re dealing with six faces. We’ll use six images, one for each face of the cube. So, no Sass but back to vanilla CSS.

That animation is a little overwhelming, right? Where do you even start?

We have six faces, so we need to perform at least six rotations so that each image gets a turn. Well, actually, we need five rotations — the last one brings us back to the first image face. If you go grab a Rubik’s Cube — or some other cube-shaped object like dice — and rotate it with your hand, you’ll have a good idea of what we’re doing.

.gallery {
  --s: 250px; /* the size */

  transform-style: preserve-3d;
  --_p: perspective(calc(2.5*var(--s)));
  animation: r 9s infinite cubic-bezier(.5, -0.5, .5, 1.5);
}

@keyframes r {
  0%, 3%   { transform: var(--_p); }
  14%, 19% { transform: var(--_p) rotateX(90deg); }
  31%, 36% { transform: var(--_p) rotateX(90deg) rotateZ(90deg); }
  47%, 52% { transform: var(--_p) rotateX(90deg) rotateZ(90deg) rotateY(-90deg); }
  64%, 69% { transform: var(--_p) rotateX(90deg) rotateZ(90deg) rotateY(-90deg) rotateX(90deg); }
  81%, 86% { transform: var(--_p) rotateX(90deg) rotateZ(90deg) rotateY(-90deg) rotateX(90deg) rotateZ(90deg); }
  97%, 100%{ transform: var(--_p) rotateX(90deg) rotateZ(90deg) rotateY(-90deg) rotateX(90deg) rotateZ(90deg) rotateY(-90deg); }
}

The transform property starts with zero rotations and, on each state, we append a new rotation on a specific axis until we reach six rotations. Then we are back to the first image.

Let’s not forget the placement of our images. Each one is applied to a face of the cube using transform:

.gallery img {
  grid-area: 1 / 1;
  width: var(--s);
  aspect-ratio: 1;
  object-fit: cover;
  transform: var(--_t,) translateZ(calc(var(--s) / 2));
}
.gallery img:nth-child(2) { --_t: rotateX(-90deg); }
.gallery img:nth-child(3) { --_t: rotateY( 90deg) rotate(-90deg); }
.gallery img:nth-child(4) { --_t: rotateX(180deg) rotate( 90deg); }
.gallery img:nth-child(5) { --_t: rotateX( 90deg) rotate( 90deg); }
.gallery img:nth-child(6) { --_t: rotateY(-90deg); }

You are probably thinking there is weird complex logic behind the values I’m using there, right? Well, no. All I did was open DevTools and play with different rotation values for each image until I got it right. It may sound stupid but, hey, it works — especially since we have a fixed number of images and we are not looking for something that supports N images.

In fact, forget the values I’m using and try to do the placement on your own as an exercise. Start with all the images stacked on top of each other, open the DevTools, and go! You will probably end up with different code and that’s totally fine. There can be different ways to position the images.

What’s the trick with the comma inside the var()? Is it a typo?

It’s not a typo so don’t remove it! If you do remove it, you will notice that it affects the placement of the first image. You can see that in my code I defined --_t for all the images except the first one because I only need a translation for it. That comma makes the variable fall back to a null value. Without the comma, we won’t have a fallback and the whole value will be invalid.

From the specification:

Note: That is, var(--a,) is a valid function, specifying that if the --a custom property is invalid or missing, the var()` should be replaced with nothing.

Random cube slider

A little bit of randomness can be a nice enhancement for this sort of animation. So, rather than rotate the cube in sequential order, we can roll the dice so to speak, and let the cube roll however it will.

Cool right? I don’t know about you, but I like this version better! It’s more interesting and the transitions are satisfying to watch. And guess what? You can play with the values to create your own random cube slider!

The logic is actual not random at all — it just appears that way. You define a transform on each keyframe that allows you to show one face and… well, that’s really it! You can pick any order you want.

@keyframes r {
  0%, 3%   { transform: var(--_p) rotate3d( 0, 0, 0,  0deg); }
  14%,19%  { transform: var(--_p) rotate3d(-1, 1, 0,180deg); }
  31%,36%  { transform: var(--_p) rotate3d( 0,-1, 0, 90deg); }
  47%,52%  { transform: var(--_p) rotate3d( 1, 0, 0, 90deg); }
  64%,69%  { transform: var(--_p) rotate3d( 1, 0, 0,-90deg); }
  81%,86%  { transform: var(--_p) rotate3d( 0, 1, 0, 90deg); }
  97%,100% { transform: var(--_p) rotate3d( 0, 0, 0,  0deg); }
}

I am using rotate3d() this time but am still relying on DevTools to find the values that feel “right” to me. Don’t try to find a relationship between the keyframes because there simply isn’t one. I’m defining separate transforms and then watching the “random” result. Make sure the first image is the first and last frames, respectively, and show a different image on each of the other frames.

You are not obligated to use a rotate3d() transform as I did. You can also chain different rotations like we did in the previous example. Play around and see what you can come up with! I will be waiting for you to share your version with me in the comments section!

Wrapping up

I hope you enjoyed this little series. We built some fun (and funny) sliders while learning a lot about all kinds of CSS concepts along the way — from grid placement and stacking order, to animation delays and transforms. We even got to play with a dash of Sass to loop through an array of elements.

And we did it all with the exact same HTML for each and every slider we made. How cool is that? CSS is dang powerful and capable of accomplishing so much without the aid of JavaScript.


CSS Infinite 3D Sliders originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.



source https://css-tricks.com/css-infinite-3d-sliders/

Friday, December 16, 2022

So, you’d like to animate the display property

The CSS Working Group gave that a thumbs-up a couple weeks ago. The super-duper conceptual proposal being that we can animate or transition from, say, display: block to display: none.

It’s a bit of a brain-twister to reason about because setting display: none on an element cancels animations. And adding it restarts animations. Per the spec:

Setting the display property to none will terminate any running animation applied to the element and its descendants. If an element has a display of none, updating display to a value other than none will start all animations applied to the element by the animation-name property, as well as all animations applied to descendants with display other than none.

That circular behavior is what makes the concept seemingly dead on arrival. But if @keyframes supported any display value other than none, then there’s no way for none to cancel or restart things. That gives non-none values priority, allowing none to do its thing only after the animation or transition has completed.

Miriam’s toot (this is what we’re really calling these, right?) explains how this might work:

We’re not exactly interpolating between, say, block and none, but allowing block to stay intact until the time things stop moving and it’s safe to apply none. These are keywords, so there are no explicit values between the two. As such, this remains a discrete animation. We’re toggling between two values once that animation is complete.

This is the Robert Flack’s example pulled straight from the discussion:

@keyframes slideaway {
  from { display: block; }
  to { transform: translateY(40px); opacity: 0;}
}

.hide {
  animation: slideaway 200ms;
  display: none;
}

This is a helpful example because it shows how the first frame sets the element to display: block, which is given priority over the underlying display: none as a non-none value. That allows the animation to run and finish without none cancelling or resetting it in the process since it only resolves after the animation.

This is the example Miriam referenced on Mastodon:

.hide {
  transition: opacity 200ms, display 200ms;
  display: none;
  opacity: 0;
}

We’re dealing with a transition this time. The underlying display value is set to none before anything happens, so it’s completely out of the document flow. Now, if we were to transition this on hover, maybe like this:

.hide:hover {
  display: block;
  opacity: 1;
}

…then the element should theoretically fade in at 200ms. Again, we’re toggling between display values, but block is given priority so the transition isn’t cancelled up front and is actually applied after opacity finishes its transition.

At least that’s how my mind is reading into it. I’m glad there are super smart people thinking these things through because I imagine there’s a ton to sort out. Like, what happens if multiple animations are assigned to an element — will none reset or cancel any of those? I’m sure everything from infinite animations, reversed directions, and all sorts of other things will be addressed in time.

But what a super cool first step!


So, you’d like to animate the display property originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.



source https://css-tricks.com/so-youd-like-to-animate-the-display-property/

Thursday, December 15, 2022

Some Links on AI-Related Stuff

Every so often, I find that the links I save to read later fall into natural groups or patterns that reveal common threads of interest. The past couple of weeks have produced a lot of thoughts about ChatGPT, an AI-powered interface that responds to requests in a chat-like exchange. Sorta like a “Hey Siri” request, but in a Discord channel.

ChatGPT is just one of several AI-flavored tech, including GitHub’s CoPilot (writing code) and Dall-E (generative images and art).

Is it the end of human development? A new and exciting way to produce art? Just cocktail party conversation fodder? There are lots of opinions…

  • A Conversation With ChatGPT (Matthias Ott) — Matthias has a conversation with ChatGPT about typography that delves into deeply theoretical thoughts on design process. My favorite is in response to whether designers should learn to code: “Ultimately, whether or not designers should learn to code is a decision that each individual designer must make for themselves, based on their own goals and circumstances. Some designers may benefit from learning to code, while others may be better served by focusing on design principles and concepts.”
  • They were supposed to replace the creative jobs last (Dave Rupert)— “As interesting a future this creates, I’m a member of an old caste of people that still believes massive gains don’t come without realized costs; or more explicitly, electricity isn’t the only cost. What if the cost we’re paying is our perception of reality itself? It’s increasingly likely that the next thing you read or watch is the product of a content extruder.”
  • I just used ChatGPT to help with a complicated equation. — A reddit user used ChatGPT to write a complex equation in Notion. There were a couple hiccups, but it worked in the end.
  • ChatGPT Creates a Working WordPress Plugin – On the First Try (WP Tavern) — Sarah Gooding reporting on a ChatGPT experiment where Johnathon Williams was able to spit out a fully-functional WordPress plugin with a simple chat command. This is the sort of thing that both terrifies me but also blows my mind-hole.
  • ChatGPT Is a Smart Computer’s Impression of a Know-It-All (Pixel Envy) — Nick Heer points to an article on The Atlantic about ChatGPT that opens with three paragraphs written by ChatGPT. It’s crazy that it comes off as naturally as it does, even if it smells slightly fishy at first.
  • Use of ChatGPT1 generated text for content on Stack Overflow is temporarily banned. (Stack Overflow) — A mild dose of #HotDrama as far as Stack Overflow users posting ChatGPT-produced code as answers.
  • Midjourney vs. human illustrators: has AI already won? (Evil Martians) — I love the experiment in this post because it’s a clear example that AI doesn’t *just* work. In its current state, at best, AI is a junior designer when put to the task of creating an image: “After two and a half hours of back and forth with the AI, I was completely exhausted and decided to just upscale the most promising result.” A bonus is that the post concludes with a list of situations where AI might realistically help the team with future work — and it ain’t an entire person’s job.
  • Quick Thoughts on AI (Collaborative Fund) — Ha! Crazy to see a chart comparing how fast ChatGPT reached one million users to other popular services. It took Facebook 10 months, but only five days for ChatGPT.

Dall-E, I want a photo of a developer sitting at a desk with his head exploding while having a chat conversation on a desktop computer with an artificial intelligence algorithm.

Not bad, not bad.


Some Links on AI-Related Stuff originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.



source https://css-tricks.com/some-links-on-ai-related-stuff/

Wednesday, December 14, 2022

Unchain My Inaccessibly-Labelled Heart

Suzy Naschansky from the HTMHell Advent Calendar:

<h2 id="article1-heading">All About Dragons</h2>  
<p>I like dragons. Blah blah blah blah blah.</p>  
<p>
  <a id="article1-read-more" aria-labelledby="article1-read-more article1-heading">Read more</a>  
</p>  

See that aria-labelledby attribute? It chains two IDs from the markup, one for the heading (#article1-heading) and one for the link (#article1-read-more). What happens there is a screenreader will replace the existing semantic label between the link tags and use the content from both elements and announce them together as a single string of text:

Read more All About Dragons

I’m always sheepish when realizing there’s something I think I should know but don’t. This is definitely one of those cases and I’m thankful as all heck that Suzy shared it.

I was actually in a situation just recently where I could’ve should’ve done this. I always try to avoid a bunch of “Read more” links on the same page but coming up with different flavors of the same thing is tough when you’re working with something like a loop of 15 posts (even though there are resources to help). And if we need to keep labels short for aesthetic reasons — design requirements and whatnot — it’s even more challenging. The aria-labelledby attribute gives me exactly what I want: consistent visual labels and more contextual announcements for assistive tech.

And this is only a thing when the text you want to use for the accessible label already exists on the page. Otherwise, you’d want to go with aria-label and with the caveat that it’s purely for interactive elements that are unable to label things accessibly with semantic HTML.

If you are working in a CMS like WordPress (which I am), you might need to do a little extra work. Like when I drop a Button block on the page, these are the options I have to work with:

Some nice options in there, but nothing to do with accessible labelling. If you’re wondering what’s buried in that Advanced panel:

Close, but no cigar.

Instead, you’ll need to edit the button in HTML mode:

But before doing that, you gotta add an ID to the heading you want to use. The Heading block has the same Advanced panel setting for adding an anchor, which’ll inject an ID on the element:

Then you can go edit the Button block in HTML mode and add the accessible-labels ID as well as an ID for the button itself. This is an example of the edited markup:

<div class="wp-block-buttons">
  <!-- wp:button -->
  <div class="wp-block-button">
    <a id="read-more-button" aria-labelledby="read-more-button heading-accessible-labels" class="wp-block-button__link wp-element-button" href="https://webaim.org/projects/million/">
      Read Report
    </a>
  </div>
  <!-- /wp:button -->
</div>

Great! But WordPress just ain’t cool with that:

You can try to resolve the issue:

Le sigh. The Button block has to be converted to a Custom HTML block. Kinda defeats the whole visual editing thing that WordPress is so good at. I did a super quick search for a plugin that might add ARIA labelling options to certain blocks, but came up short. Seems like a ripe opportunity to make one or submit PRs for the blocks that could use those options.


Unchain My Inaccessibly-Labelled Heart originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.



source https://css-tricks.com/unchain-my-inaccessibly-labelled-heart/

Holiday Snowtacular 2022

We’ve got ourselves a real holiday treat! Join host Alex Trost from the Frontend Horse community for the Holiday Snowtacular 2022 this Friday, December 16.

There’s a lineup of 12 awesome speakers — including Chris Coyier, Cassidy Williams, Kevin Powell, and Angie Jones — each discussing various front-end and web dev topics. It’s like the 12 days of Christmas, but wrapped up in a four-hour session for web nerds like us.

It’s a real good cause, too. The event is free, but includes fundraising Doctors Without Borders with a goal of reaching $20,000. You can donate here any time and anything you give will be matched by the event’s sponors. So, come for the front-end fun and help a great cause in the process.

To Shared Link — Permalink on CSS-Tricks


Holiday Snowtacular 2022 originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.



source https://css-tricks.com/holiday-snowtacular-2022/