Developer News

Use CSS Variables instead of React Context

Css Tricks - Fri, 02/19/2021 - 6:05am

Turns out you can use several different libraries to pass color information around components. Or, you could use custom properties, built right into CSS, have no decline in your own developer experience, and deliver a faster experience to your users. Kent proves it here, with demos.

For the record, you could go a step further than Kent has here and not use CSS-in-JS at all, but still be updating CSS custom properties from button clicks in React and managing the state there and such. I’m telling ya, one of the main jobs of a UI component library like React is managing state, and CSS might as well know about that state so you can use it to do any styling you need to do.

Wait, not use CSS-in-JS? Kent:

I’ve never been so productive working with CSS than when I added a real programming language to it.

Extreme side eye, Kent.

We should be calling it CSS-in-React, also, since React is the only major framework that doesn’t have a blessed solution for styling.

Direct Link to ArticlePermalink

The post Use CSS Variables instead of React Context appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Mistakes I’ve Made as an Engineering Manager

Css Tricks - Thu, 02/18/2021 - 12:39pm

I’ve been a manager for many years at companies of different scale. Through these experiences, I’ve done my share of learning, and made some mistakes along that way that were important lessons for me. I want to share those with you.

But before diving in, I want to mention a strong caveat that my advice may be unique to my situation because I’m white and a woman in tech. My experiences may be relevant to that point of view, but your mileage may vary.

Another huge caveat: I’m sharing mistakes I’ve made so far in the interest of helping others, but I’m sure I’m not done making mistakes, either. I don’t have it all figured it out, I’m still on this journey.

Credit: WoCinTechChat Mistake 1: Thinking people give feedback the way they want to receive it

Feedback is one of the most important tools you have as a manager, but it can also be incredibly disruptive with poor execution. One of the hardest things I’ve had to learn is that humans aren’t pure functions: an input that works one day and gets one result, then again another day and get an entirely different result.

The same is true of how people give and receive feedback: someone may give you feedback in a particular way, but they prefer to receive much differently when it comes to themselves.

How do you get around this? Asking helps. I’ve started doing an exercise with my team where I ask the group as a whole how they would like to get feedback. Not only does it open up ideas, but it also helps that each individual has to think for themselves how they prefer to receive feedback. Normalizing this type of vulnerability and self-reflection can help us all feel like partners, instead of some top-down edict.

Another thing that’s helped? Asking folks directly in a one-on-one meeting if they have feedback for me as a manager, and following up with an anonymous survey. Again, it makes things feel less one-sided and provides everyone the opportunity to say things that they might not want to say directly to my face, which I know can be tough.

And lastly, if something comes up, addressing it immediately can be helpful. There’s nothing worse than your manager having an issue with something you did and only finding out about it three months later, especially if it’s tied to a performance review that you could have impacted had they been transparent earlier.

The truth is that even my advice here is imperfect. Feedback is tough. Being honest and improving together as a team is awkward. It’s incredibly worth it, though. That’s where the real growth is. That said, no two people are alike, no two groups are alike, and you may have to use your best judgement given the situation at hand.

Mistake 2: Trying to do everything yourself as a manager is the best way to help

Years ago, I managed a woman who was bright, talented, capable, and an all around pleasure. She was sort of new to the industry and could come across as timid, so I did my best to be a poop umbrella for her, fighting battles behind the scenes to set her up for success. She was on a steady track to land a senior role. Even after I decided to leave the company, I let the next manager know this person is track for a senior position in the next few months.

Then I moved to another city. Years later, I met up with the woman and was shocked to learn she never got the position.

Here’s what I learned: her promotion wasn’t the same high priority for the capable hands I left her in as it was for me. The team was challenged with a million other things that took center stage to the extent that her promotion fell off the radar. But even more than that, what became very clear to me was that all of that “protection” I thought I had set up for her didn’t really serve her well for the long haul. For example, I didn’t teach her how to advocate for herself or how to navigate the system. I vowed never to make that mistake again.

This is tough! If you’re strong and care about your team as people, it can feel very unnatural to teach someone to advocate instead of moving things out of their way themselves. And the point is not to throw that person into the fire. The point is to care. Are you teaching the things they need to learn? Are they really growing under you? Feeling like you’re protecting someone at all costs also lead to your own ego trip, too, which threatens progress.

Try to think through what skills someone needs to succeed without you. Teach those things incrementally. Sure, this is easy advice to say, but it’s really hard to do in the thick of things. Spend some time with it, and think through ways you can inject that learning into everyday work and interactions.

Credit: Charles Deluvio on Unsplash Mistake 3: Communicating something one time is enough

No one likes to feel like they’re repeating themselves. It’s annoying to say someone more than once, and it’s annoying to hear something over and again. But if you have a big enough group and there’s enough going on, things are going to slip through the cracks, so repetition becomes an important tool to make things stick. The trick is to say the same things, but in different ways.

There was a time last year when I asked my team to do something and none of them did it. What happened there? Given that it’s a team of highly efficient, strong collaborators, do you think they just all table-flipped and didn’t take action? Not a chance. I was the one who wasn’t clear. In fact, you can probably guess that if a whole group of people don’t understand or take action, the chance is that you, the manager, are the common denominator for why something is blocked. Not only did I not repeat myself enough to be clear, I didn’t align anyone with the why of the purpose of the task. It’s pretty easy to forget or not prioritize doing something if you have no clue why you’re doing it. Repeat yourself and align the group with the importance of the task and you’ll likely have a better result.

Think of all the ways we have to communicate these days: chats, emails, video meetings, texts, document comments, and so much more. And because some people communicate better in one medium than another, using all of the platforms have in various mediums becomes a strategy for repetition without nagging.

I’ve found that what work best is allowing everyone to own the information themselves. For example, if your team practices career laddering, the individual can read aloud each of the ladders in one-on-one and then talk you through their responses to each item. That way, you’re not lecturing — they are owning where they are and what the next steps are as you guide them along.

Mistake 4: You have to have everything together all the time

Some folks think that management looks like a steel fortress of preparedness and authority. I’m not so sure about that.

If something goes wrong, are you more likely to tell the manager acts as though they have everything together all the time, or the manager owns their mistakes? The truth is that your team needs to know you’re human. You can’t fix problems if you don’t know about them, and no one will tell you about them unless you make space for that.

One time, the night before a big release, someone on the team pushed a change that created thousands upon thousands of calls to a service that, in turn, thought it was the target of a DDoS attack, which then shut down our access. Here’s a moment when a lot of folks could have panicked and blamed one another. Instead, we giggled wildly, jumped into chat and on calls, fixed it, and kept going.

I couldn’t have been more proud of the team that day. Their response was wonderful. And it makes all the difference in how we work together, recover, and iterate.

You’re the manager, so if someone is going to show vulnerability first, it’s easiest on team dynamics if it’s you. You can try this by admitting you’re having a bad day, that you don’t understand something, or that made a mistake. You don’t have to do this constantly, a little is helpful.

This is way tougher if you are an underrepresented minority in tech. People will definitely think of an admitted mistake coming from one person as humility and another as failure. I myself struggle with this, too. I think it’s ok to admit that and feel the situation out given your circumstance.

Being a manager is tough. Your mistakes impact people, and that feeling of pressure can be a little isolating. I’ve made all of the mistakes above and more. I feel it’s critical to share so that when we encounter pitfalls, we don’t feel so alone and have a potential path forward.

The post Mistakes I’ve Made as an Engineering Manager appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Recipe websites, data modeling, and user experience

Css Tricks - Thu, 02/18/2021 - 11:19am

Simeon Griggs with some nice UX ideas for a recipe website:

  • No math. Swap between units and adjust servings on-the-fly.
  • Offer alternative ingredients.
  • Re-list the ingredient amounts when they’re referenced in the instructions.

I totally agree, especially on that last one:

Of all our improvements I think this is my favourite.

A typical recipe layout contains ingredients with amounts at the start. Then, a bullet point list of instructions to perform the method.

The method though typically does not reference those amounts again, so if you don’t prepare all your amounts ahead of time (which is what you’re probably supposed to do but come on who does that) you’ll have to keep scanning back and forward.

Part of what makes this stuff possible is how you set up the data model. For example, an ingredient might be an Array instead of a String so that you’re set up for offering alternatives right out of the gate.

Direct Link to ArticlePermalink

The post Recipe websites, data modeling, and user experience appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Let’s Create a Custom Audio Player

Css Tricks - Thu, 02/18/2021 - 6:27am

HTML has a built-in native audio player interface that we get simply using the <audio> element. Point it to a sound file and that’s all there is to it. We even get to specify multiple files for better browser support, as well as a little CSS flexibility to style things up, like giving the audio player a border, some rounded corners, and maybe a little padding and margin.

But even with all that… the rendered audio player itself can look a little, you know, plain.

Did you know it’s possible to create a custom audio player? Of course we can! While the default <audio> player is great in many cases, having a custom player might suit you better, like if you run a podcast and an audio player is the key element on a website for the podcast. Check out the sweet custom player Chris and Dave set up over at the ShopTalk Show website.

The audio player fits in seamlessly with other elements on the page, sporting controls that complement the overall design.

We’re going to take stab at making our own player in this post. So, put on your headphones, crank up some music, and let’s get to work!

The elements of an audio player

First, let’s examine the default HTML audio players that some of the popular browsers provide.

Blink Firefox Internet Explorer

If our goal is to match the functionality of these examples, then we need to make sure our player has:

  • a play/pause button,
  • a seek slider,
  • the current time indicator,
  • the duration of the sound file,
  • a way to mute the audio, and
  • a volume control slider.

Let’s say this is the design we’re aiming for:

We’re not going for anything too fancy here: just a proof of concept sorta thing that we can use to demonstrate how to make something different than what default HTML provides.

Basic markup, styling and scripts for each element

We should first go through the semantic HTML elements of the player before we start building features and styling things. We have plenty of elements to work with here based on the elements we just listed above.

Play/pause button

I think the HTML element appropriate for this button is the <button> element. It will contain the play icon, but the pause icon should also be in this button. That way, we’re toggling between the two rather than taking up space by displaying both at the same time.

Something like this in the markup:

<div id="audio-player-container"> <p>Audio Player</p> <!-- swaps with pause icon --> <button id="play-icon"></button> </div>

So, the question becomes: how do we swap between the two buttons, both visually and functionally? the pause icon will replace the play icon when the play action is triggered. The play button should display when the audio is paused and the pause button should display when the audio is playing.

Of course, a little animation could take place as the icon transitions from the play to pause. What would help us accomplish that is Lottie, a library that renders Adobe After Effects animations natively. We don’t have to create the animation on After Effects though. The animated icon we’re going to use is provided for free by Icons8.

New to Lottie? I wrote up a thorough overview that covers how it works.

In the meantime, permit me to describe the following Pen:

CodePen Embed Fallback

The HTML section contains the following:

  • a container for the player,
  • text that briefly describes the container, and
  • a <button> element for the play and pause actions.

The CSS section includes some light styling. The JavaScript is what we need to break down a bit because it’s doing several things:

// imports the Lottie library via Skypack import lottieWeb from 'https://cdn.skypack.dev/lottie-web'; // variable for the button that will contain both icons const playIconContainer = document.getElementById('play-icon'); // variable that will store the button’s current state (play or pause) let state = 'play'; // loads the animation that transitions the play icon into the pause icon into the referenced button, using Lottie’s loadAnimation() method const animation = lottieWeb.loadAnimation({ container: playIconContainer, path: 'https://maxst.icons8.com/vue-static/landings/animated-icons/icons/pause/pause.json', renderer: 'svg', loop: false, autoplay: false, name: "Demo Animation", }); animation.goToAndStop(14, true); // adds an event listener to the button so that when it is clicked, the the player toggles between play and pause playIconContainer.addEventListener('click', () => { if(state === 'play') { animation.playSegments([14, 27], true); state = 'pause'; } else { animation.playSegments([0, 14], true); state = 'play'; } });

Here’s what the script is doing, minus the code:

  • It imports the Lottie library via Skypack.
  • It references the button that will contain both icons in a variable.
  • It defines a variable that will store the button’s current state (play or pause).
  • It loads the animation that transitions the play icon into the pause icon into the referenced button, using Lottie’s loadAnimation() method.
  • It displays the play icon on load since the audio is initially paused.
  • It adds an event listener to the button so that when it is clicked, the the player toggles between play and pause.
Current time and duration

The current time is like a progress indicate that shows you how much time has elapsed from the start of the audio file. The duration? That’s just how long the sound file is.

A <span> element is okay to display these. The <span> element for the current time, which is to be updated every second, has a default text content of 0:00. On the other side, the one for duration is the duration of the audio in mm:ss format.

<div id="audio-player-container"> <p>Audio Player</p> <button id="play-icon"></button> <span id="current-time" class="time">0:00</span> <span id="duration" class="time">0:00</span> </div> Seek slider and volume control slider

We need a way to move to any point in time in the sound file. So, if I want to skip ahead to the halfway point of the file, I can simply click and drag a slider to that spot in the timeline.

We also need a way to control the sound volume. That, too, can be some sort of click-and-drag slider thingy.

I would say <input type="range"> is the right HTML element for both of these features.

<div id="audio-player-container"> <p>Audio Player</p> <button id="play-icon"></button> <span id="current-time" class="time">0:00</span> <input type="range" id="seek-slider" max="100" value="0"> <span id="duration" class="time">0:00</span> <input type="range" id="volume-slider" max="100" value="100"> </div>

Styling range inputs with CSS is totally possible, but I’ll tell you what: it is difficult for me to wrap my head around. This article will help. Mad respect to you, Ana. Handling browser support with all of those vendor prefixes is a CSS trick in and of itself. Look at all the code needed on input[type="range"] to get a consistent experience:

input[type="range"] { position: relative; -webkit-appearance: none; width: 48%; margin: 0; padding: 0; height: 19px; margin: 30px 2.5% 20px 2.5%; float: left; outline: none; } input[type="range"]::-webkit-slider-runnable-track { width: 100%; height: 3px; cursor: pointer; background: linear-gradient(to right, rgba(0, 125, 181, 0.6) var(--buffered-width), rgba(0, 125, 181, 0.2) var(--buffered-width)); } input[type="range"]::before { position: absolute; content: ""; top: 8px; left: 0; width: var(--seek-before-width); height: 3px; background-color: #007db5; cursor: pointer; } input[type="range"]::-webkit-slider-thumb { position: relative; -webkit-appearance: none; box-sizing: content-box; border: 1px solid #007db5; height: 15px; width: 15px; border-radius: 50%; background-color: #fff; cursor: pointer; margin: -7px 0 0 0; } input[type="range"]:active::-webkit-slider-thumb { transform: scale(1.2); background: #007db5; } input[type="range"]::-moz-range-track { width: 100%; height: 3px; cursor: pointer; background: linear-gradient(to right, rgba(0, 125, 181, 0.6) var(--buffered-width), rgba(0, 125, 181, 0.2) var(--buffered-width)); } input[type="range"]::-moz-range-progress { background-color: #007db5; } input[type="range"]::-moz-focus-outer { border: 0; } input[type="range"]::-moz-range-thumb { box-sizing: content-box; border: 1px solid #007db5; height: 15px; width: 15px; border-radius: 50%; background-color: #fff; cursor: pointer; } input[type="range"]:active::-moz-range-thumb { transform: scale(1.2); background: #007db5; } input[type="range"]::-ms-track { width: 100%; height: 3px; cursor: pointer; background: transparent; border: solid transparent; color: transparent; } input[type="range"]::-ms-fill-lower { background-color: #007db5; } input[type="range"]::-ms-fill-upper { background: linear-gradient(to right, rgba(0, 125, 181, 0.6) var(--buffered-width), rgba(0, 125, 181, 0.2) var(--buffered-width)); } input[type="range"]::-ms-thumb { box-sizing: content-box; border: 1px solid #007db5; height: 15px; width: 15px; border-radius: 50%; background-color: #fff; cursor: pointer; } input[type="range"]:active::-ms-thumb { transform: scale(1.2); background: #007db5; }

Whoa! What does even mean, right?

Styling the progress section of range inputs is a tricky endeavor. Firefox provides the ::-moz-range-progress pseudo-element while Internet Explorer provides ::-ms-fill-lower. As WebKit browsers do not provide any similar pseudo-element, we have to use the ::before pseudo-element to improvise the progress. That explains why, if you noticed, I added event listeners in the JavaScript section to set custom CSS properties (e.g. --before-width) that update when the input event is fired on each of the sliders.

One of the native HTML <audio> examples we looked at earlier shows the buffered amount of the audio. The --buffered-width property specifies the amount of the audio, in percentage, that the user can play through to without having to wait for the browser to download. I imitated this feature with the linear-gradient() function on the track of the seek slider. I used the rgba() function in the linear-gradient() function for the color stops to show transparency. The buffered width has a much deeper color compared to the rest of the track. However, we would treat the actual implementation of this feature much later.

Volume percentage

This is to display the percentage volume. The text content of this element is updated as the user changes the volume through the slider. Since it is based on user input, I think this element should be the <output> element.

<div id="audio-player-container"> <p>Audio Player</p> <button id="play-icon"></button> <span id="current-time" class="time">0:00</span> <input type="range" id="seek-slider" max="100" value="0"> <span id="duration" class="time">0:00</span> <output id="volume-output">100</output> <input type="range" id="volume-slider" max="100" value="100"> </div> Mute button

Like for the play and pause actions, this should be in a <button> element. Luckily for us, Icons8 also has an animated mute icon. So we would use the Lottie library here just as we did for the play/pause button.

<div id="audio-player-container"> <p>Audio Player</p> <button id="play-icon"></button> <span id="current-time" class="time">0:00</span> <input type="range" id="seek-slider" max="100" value="0"> <span id="duration" class="time">0:00</span> <output id="volume-output">100</output> <input type="range" id="volume-slider" max="100" value="100"> <button id="mute-icon"></button> </div>

That’s all of the basic markup, styling and scripting we need at the moment!

CodePen Embed Fallback Working on the functionality

The HTML <audio> element has a preload attribute. This attribute gives the browser instructions for how to load the audio file. It accepts one of three values:

  • none – indicates that the browser should not load the audio at all (unless the user initiates the play action)
  • metadata – indicates that only the metadata (like length) of the audio should be loaded
  • auto – loads the complete audio file

An empty string is equivalent to the auto value. Note, however, that these values are merely hints to the browser. The browser does not have to agree to these values. For example, if a user is on a cellular network on iOS, Safari does not load any part of an audio, regardless of the preload attribute, except the user triggers the play action. For this player, we would use the metadata value since it doesn’t require much overhead and we want to display the length of the audio.

What would help us accomplish the features our audio player should have is the JavaScript HTMLMediaElement interface, which the HTMLAudioElement interface inherits. For our audio player code to be as self-explanatory as possible, I’d divide the JavaScript into two sections: presentation and functionality.

First off, we should create an <audio> element in the audio player that has the basic features we want:

<div id=”audio-player-container”> <audio src=”my-favourite-song.mp3” preload=”metadata” loop> <button id="play-icon"></button> <!-- ... --> </div> Display the audio duration

The first thing we want to display on the browser is the duration of the audio, when it is available. The HTMLAudioElement interface has a duration property, which returns the duration of the audio, returned in seconds units. If it is unavailable, it returns NaN.

We’ve set preload to metadata in this example, so the browser should provide us that information up front on load… assuming it respects preload. Since we’d be certain that the duration will be available when the browser has downloaded the metadata of the audio, we display it in our handler for the loadedmetadata event, which the interface also provides:

const audio = document.querySelector('audio'); audio.addEventListener('loadedmetadata', () => { displayAudioDuration(audio.duration); });

That’s great but, again, we get the duration in second. We probably should convert that to a mm:ss format:

const calculateTime = (secs) => { const minutes = Math.floor(secs / 60); const seconds = Math.floor(secs % 60); const returnedSeconds = seconds < 10 ? `0${seconds}` : `${seconds}`; return `${minutes}:${returnedSeconds}`; }

We’re using Math.floor() because the seconds returned by the duration property typically come in decimals.

The third variable, returnedSeconds, is necessary for situations where the duration is something like 4 minutes and 8 seconds. We would want to return 4:08, not 4:8.

More often than not, the browser loads the audio faster than usual. When this happens, the loadedmetadata event is fired before its listener can be added to the <audio> element. Therefore, the audio duration is not displayed on the browser. Nevertheless, there’s a hack. The HTMLMediaElement has a property called readyState. It returns a number that, according to MDN Web Docs, indicates the readiness state of the media. The following describes the values:

  • 0 – no data about the media is available.
  • 1 – the metadata attributes of the media are available.
  • 2 – data is available, but not enough to play more than a frame.
  • 3 – data is available, but for a little amount of frames from the current playback position.
  • 4 – data is available, such that the media can be played through to the end without interruption.

We want to focus on the metadata. So our approach is to display the duration if the metadata of the audio is available. If it is not available, we add the event listener. That way, the duration is always displayed.

const audio = document.querySelector('audio'); const durationContainer = document.getElementById('duration'); const calculateTime = (secs) => { const minutes = Math.floor(secs / 60); const seconds = Math.floor(secs % 60); const returnedSeconds = seconds < 10 ? `0${seconds}` : `${seconds}`; return `${minutes}:${returnedSeconds}`; } const displayDuration = () => { durationContainer.textContent = calculateTime(audio.duration); } if (audio.readyState > 0) { displayDuration(); } else { audio.addEventListener('loadedmetadata', () => { displayDuration(); }); } Seek slider

The default value of the range slider’s max property is 100. The general idea is that when the audio is playing, the thumb is supposed to be “sliding.” Also, it is supposed to move every second, such that it gets to the end of the slider when the audio ends.

Notwithstanding, if the audio duration is 150 seconds and the value of the slider’s max property is 100, the thumb will get to the end of the slider before the audio ends. This is why it is necessary to set the value of the slider’s max property to the audio duration in seconds. This way, the thumb gets to the end of the slider when the audio ends. Recall that this should be when the audio duration is available, when the browser has downloaded the audio metadata, as in the following:

const seekSlider = document.getElementById('seek-slider'); const setSliderMax = () => { seekSlider.max = Math.floor(audio.duration); } if (audio.readyState > 0) { displayDuration(); setSliderMax(); } else { audio.addEventListener('loadedmetadata', () => { displayDuration(); setSliderMax(); }); } Buffered amount

As the browser downloads the audio, it would be nice for the user to know how much of it they can seek to without delay. The HTMLMediaElement interface provides the buffered and seekable properties. The buffered property returns a TimeRanges object, which indicates the chunks of media that the browser has downloaded. According to MDN Web Docs, a TimeRanges object is a series of non-overlapping ranges of time, with start and stop times. The chunks are usually contiguous, unless the user seeks to another part in the media. The seekable property returns a TimeRanges object, which indicates “seekable” parts of the media, irrespective of whether they’ve been downloaded or not.

Recall that the preload="metadata" attribute is present in our <audio> element. If, for example the audio duration is 100 seconds, the buffered property returns a TimeRanges object similar to the following:

When the audio has started playing, the seekable property would return a TimeRanges object similar to the following:

It returns multiple chunks of media because, more often than not, byte-range requests are enabled on the server. What this means is that multiple parts of the media can be downloaded simultaneously. However, we want to display the buffered amount closest to the current playback position. That would be the first chunk (time range 0 to 20). That would be the first and last chunk from the first image. As the audio starts playing, the browser begins to download more chunks. We would want to display the one closest to the current playback position, which would be the current last chunk returned by the buffered property. The following snippet would store in the variable, bufferedAmount, i.e. the time for the end of the last range in the TimeRanges object returned by the buffered property.

const audio = document.querySelector('audio'); const bufferedAmount = audio.buffered.end(audio.buffered.length - 1);

This would be 20 from the 0 to 20 range in the first image. The following snippet stores in the variable, seekableAmount, the time for the end of the last range in the TimeRanges object returned by the seekable property.

const audio = document.querySelector('audio'); const seekableAmount = audio.seekable.end(audio.seekable.length - 1);

Nevertheless, this would be 100 from the 90 to 100 range in the second image, which is the entire audio duration. Note that there are some holes in the TimeRanges object as the browser only downloads some parts of the audio. What this means is that the entire duration would be displayed to the user as the buffered amount. Meanwhile, some parts in the audio are not available yet. Because this won’t provide the best user experience, the first snippet is what we should use.

As the browser downloads the audio, the user should expect that the buffered amount on the slider increases in width. The HTMLMediaElement provides an event, the progress event, which fires as the browser loads the media. Of course, I’m thinking what you’re thinking! The buffered amount should be incremented in the handler for the audio’s progress event.

Finally, we should actually display the buffered amount on the seek slider. We do that by setting the property we talked about earlier, --buffered-width, as a percentage of the value of the slider’s max property. Yes, in the handler for the progress event too. Also, because of the browser loading the audio faster than usual, we should update the property in the loadedmetadata event and its preceding conditional block that checks for the readiness state of the audio. The following Pen combines all that we’ve covered so far:

CodePen Embed Fallback Current time

As the user slides the thumb along the range input, the range value should be reflected in the <span> element containing the current time of the audio. This tells the user the current playback position of the audio. We do this in the handler of the slider’s input event listener.

If you think the correct event to listen to should be the change event, I beg to differ. Say the user moved the thumb from value 0 to 20. The input event fires at values 1 through to 20. However, the change event will fire only at value 20. If we use the change event, it will not reflect the playback position from values 1 to 19. So, I think the input event is appropriate. Then, in the handler for the event, we pass the slider’s value to the calculateTime() function we defined earlier.

We created the function to take time in seconds and return it in a mm:ss format. If you’re thinking, Oh, but the slider’s value is not time in seconds, let me explain. Actually, it is. Recall that we set the value of the slider’s max property to the audio duration, when it is available. Let’s say the audio duration is 100 seconds. If the user slides the thumb to the middle of the slider, the slider’s value will be 50. We wouldn’t want 50 to appear in the current time box because it is not in accordance with the mm:ss format. When we pass 50 to the function, the function returns 0:50 and that would be a better representation of the playback position.

I added the snippet below to our JavaScript.

const currentTimeContainer = document.getElementById('current-time'); seekSlider.addEventListener('input', () => { currentTimeContainer.textContent = calculateTime(seekSlider.value); });

To see it in action, you can move the seek slider’s thumb back and forth in the following Pen:

CodePen Embed Fallback Play/pause

Now we’re going to set the audio to play or pause according to the respective action triggered by the user. If you recall, we created a variable, playState, to store the state of the button. That variable is what will help us know when to play or pause the audio. If its value is play and the button is clicked, our script is expected to perform the following actions:

  • play the audio
  • change the icon from play to pause
  • change the playState value to pause

We already implemented the second and third actions in the handler for the button’s click event. What we need to do is to add the statements to play and pause the audio in the event handler:

playIconContainer.addEventListener('click', () => { if(playState === 'play') { audio.play(); playAnimation.playSegments([14, 27], true); playState = 'pause'; } else { audio.pause(); playAnimation.playSegments([0, 14], true); playState = 'play'; } });

It is possible that the user will want to seek to a specific part in the audio. In that case, we set the value of the audio’s currentTime property to the seek slider’s value. The slider’s change event will come in handy here. If we use the input event, various parts of the audio will play in a very short amount of time.

Recall our scenario of 1 to 20 values. Now imagine the user slides the thumb from 1 to 20 in, say, two seconds. That’s 20 seconds audio playing in two seconds. It’s like listening to Busta Rhymes on 3× speed. I’d suggest we use the change event. The audio will only play after the user is done seeking. This is what I’m talking about:

seekSlider.addEventListener('change', () => { audio.currentTime = seekSlider.value; });

With that out of the way, something needs to be done while the audio is playing. That is to set the slider’s value to the current time of the audio. Or move the slider’s thumb by one tick every second. Since the audio duration and the slider’s max value are the same, the thumb gets to the end of the slider when the audio ends. Now the timeupdate event of the HTMLMediaElement interface should be the appropriate event for this. This event is fired as the value of the media’s currentTime property is updated, which is approximately four times in one second. So in the handler for this event, we could set the slider’s value to the audio’s current time. This should work just fine:

audio.addEventListener('timeupdate', () => { seekSlider.value = Math.floor(audio.currentTime); });

However, there are some things to take note of here:

  1. As the audio is playing, and the seek slider’s value is being updated, a user is unable to interact with the slider. If the audio is paused, the slider won’t be able to receive input from the user because it is constantly being updated.
  2. In the handler, we update the value of the slider but its input event does not fire. This is because the event only fires when a user updates the slider’s value on the browser, and not when it is updated programmatically.

Let’s consider the first issue.

To be able to interact with the slider while the audio is playing, we would have to pause the process of updating it’s value when it receives input. Then, when the slider loses focus, we resume the process. But, we don’t have access to this process. My hack would be to use the requestAnimationFrame() global method for the process. But this time, we won’t be using the timeupdate event for this because it still won’t work. The animation would play forever until the audio is paused, and that’s not what we want. Therefore, we use the play/pause button’s click event.

To use the requestAnimationFrame() method for this feature, we have to accomplish these steps:

  1. Create a function to keep our “update slider value” statement.
  2. Initialize a variable in the function that was previously created to store the request ID returned by the function (that will be used to pause the update process).
  3. Add the statements in the play/pause button click event handler to start and pause the process in the respective blocks.

This is illustrated in the following snippet:

let rAF = null; const whilePlaying = () => { seekSlider.value = Math.floor(audio.currentTime); rAF = requestAnimationFrame(whilePlaying); } playIconContainer.addEventListener('click', () => { if(playState === 'play') { audio.play(); playAnimation.playSegments([14, 27], true); requestAnimationFrame(whilePlaying); playState = 'pause'; } else { audio.pause(); playAnimation.playSegments([0, 14], true); cancelAnimationFrame(rAF); playState = 'play'; } });

But this doesn’t exactly solve our problem. The process is only paused when the audio is paused. We also need to pause the process, if it is in execution (i.e. if the audio is playing), when the user wants to interact with the slider. Then, after the slider loses focus, if the process was ongoing before (i.e. if the audio was playing), we start the process again. For this, we would use the slider’s input event handler to pause the process. To start the process again, we would use the change event because it is fired after the user is done sliding the thumb. Here is the implementation:

seekSlider.addEventListener('input', () => { currentTimeContainer.textContent = calculateTime(seekSlider.value); if(!audio.paused) { cancelAnimationFrame(raf); } }); seekSlider.addEventListener('change', () => { audio.currentTime = seekSlider.value; if(!audio.paused) { requestAnimationFrame(whilePlaying); } });

I was able to come up with something for the second issue. I added the statements in the seek slider’s input event handlers to the whilePlaying() function. Recall that there are two event listeners for the slider’s input event: one for the presentation, and the other for the functionality. After adding the two statements from the handlers, this is how our whilePlaying() function looks:

const whilePlaying = () => { seekSlider.value = Math.floor(audio.currentTime); currentTimeContainer.textContent = calculateTime(seekSlider.value); audioPlayerContainer.style.setProperty('--seek-before-width', `${seekSlider.value / seekSlider.max * 100}%`); raf = requestAnimationFrame(whilePlaying); }

Note that the statement on the fourth line is the seek slider’s appropriate statement from the showRangeProgress() function we created earlier in the presentation section.

Now we’re left with the volume-control functionality. Whew! But before we begin working on that, here’s a Pen covering all we’ve done so far:

CodePen Embed Fallback Volume-control

For volume-control, we’re utilizing the second slider, #volume-slider. When the user interacts with the slider, the slider’s value is reflected in the volume of the audio and the <output> element we created earlier.

The slider’s max property has a default value of 100. This makes it easy to display its value in the <output> element when it is updated. We could implement this in the input event handler of the slider. However, to implement this in the volume of the audio, we’re going to have to do some math.

The HTMLMediaElement interface provides a volume property, which returns a value between 0 and 1, where 1 being is the loudest value. What this means is if the user sets the slider’s value to 50, we would have to set the volume property to 0.5. Since 0.5 is a hundredth of 50, we could set the volume to a hundredth of the slider’s value.

const volumeSlider = document.getElementById('volume-slider'); const outputContainer = document.getElementById('volume-output'); volumeSlider.addEventListener('input', (e) => { const value = e.target.value; outputContainer.textContent = value; audio.volume = value / 100; });

Not bad, right?

Muting audio

Next up is the speaker icon, which is clicked to mute and unmute the audio. To mute the audio, we would use its muted property, which is also available via HTMLMediaElement as a boolean type. Its default value is false, which is unmuted. To mute the audio, we set the property to true. If you recall, we added a click event listener to the speaker icon for the presentation (the Lottie animation). To mute and unmute the audio, we should add the statements to the respective conditional blocks in that handler, as in the following:

const muteIconContainer = document.getElementById('mute-icon'); muteIconContainer.addEventListener('click', () => { if(muteState === 'unmute') { muteAnimation.playSegments([0, 15], true); audio.muted = true; muteState = 'mute'; } else { muteAnimation.playSegments([15, 25], true); audio.muted = false; muteState = 'unmute'; } }); Full demo

Here’s the full demo of our custom audio player in all its glory!

CodePen Embed Fallback

But before we call it quits, I’d like to introduce something — something that will give our user access to the media playback outside of the browser tab where our custom audio player lives.

Permit me to introduce to you, drumroll, please…

The Media Session API

Basically, this API lets the user pause, play, and/or perform other media playback actions, but not with our audio player. Depending on the device or the browser, the user initiates these actions through the notification area, media hubs, or any other interface provided by their browser or OS. I have another article just on that for you to get more context on that.

The following Pen contains the implementation of the Media Session API:

CodePen Embed Fallback

If you view this Pen on your mobile, take a sneak peek at the notification area. If you’re on Chrome on your computer, check the media hub. If your smartwatch is paired, I’d suggest you look at it. You could also tell your voice assistant to perform some of the actions on the audio. Ten bucks says it’ll make you smile. &#x1f913;

One more thing…

If you need an audio player on a webpage, there’s a high chance that the page contains other stuff. That’s why I think it’s smart to group the audio player and all the code needed for it into a web component. This way, the webpage possesses a form of separation of concerns. I transferred everything we’ve done into a web component and came up with the following:

CodePen Embed Fallback

Wrapping up, I’d say the possibilities of creating a media player are endless with the HTMLMediaElement interface. There’s so many various properties and methods for various functions. Then there’s the Media Session API for an enhanced experience.

What’s the saying? With great power comes great responsibility, right? Think of all the various controls, elements, and edge cases we had to consider for what ultimately amounts to a modest custom audio player. Just goes to show that audio players are more than hitting play and pause. Properly speccing out functional requirements will definitely help plan your code in advance and save you lots of time.

The post Let’s Create a Custom Audio Player appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Barebones CSS for Fluid Images

Css Tricks - Thu, 02/18/2021 - 6:25am

Zach takes a look at some fundamental HTML+CSS usage for fluid, responsive images. Most of it, I’d say, is what you’d expect, but things get weird when srcset gets involved.

I poked my way through, and in addition to the weird thing Zach noted, wanted to add one more thing. Let’s start like this:

<img src="./img.jpg" alt="" />

With no other CSS involved, this renders at the “intrinsic size” of the image. Say the original image is 400px wide, it renders 400px wide.

We should be putting width and height attributes on images, because it allows the browser to make space for them even before they are downloaded (even when they are fluid, which is super cool). So:

<img src="./img.jpg" alt="" width="400" height="300" />

Also nothing terribly weird there. Even if we slap max-width: 100% in the CSS, that’ll do what we want: preserving space, behave fluidly, and not growing bigger than it should.

But let’s hold off on the max-width: 100% thing for a second. If we just use srcset and set up multiple sources.

<img src="./img.jpg" alt="" srcset="./img-200.jpg 200w, ./img-400.jpg 400w" />

BAM, we blow out the width of the thing.

That won’t render at 200px or 400px—it’ll actually render at 100vw, believe it or not. I think that’s because that’s the default sizes value. I normally think of the sizes attribute as not information about anything to do with actual layout, but just information for the browser to choose a source. But that’s not true here. It really does effect layout (in all browsers I tested). Here’s proof:

CodePen Embed Fallback

See the little one below it where all I change is the sizes.

Anyway that’s not what Zach honed in on, but it’s similar. Let’s put back the responsible thing and add in width and height attributes.

<img src="./img.jpg" alt="" width="200" height="137" srcset="./img-200.jpg 200w, ./img-400.jpg 200w" />

No more blowout (with or without sizes) but now we have a new weird problem. This is basically like saying max-width: 200px. Even though we have sources that are wider than 200px, we’ve capped the width at 200px. Zach puts it like:

Using max-width: 100% constrains the image to the container, but be careful when you use this with srcset—it may cap smaller than you want when using [width]! Pair with width: auto to fix this.

Zach’s final snippet is this, which I think reigns in all the weirdness:

img { max-width: 100%; } img[width] { width: auto; /* Defer to max-width */ } img[width][height] { height: auto; /* Preserve aspect ratio */ } /* Let SVG scale without boundaries */ img[src$=".svg"] { width: 100%; height: auto; max-width: none; }

Direct Link to ArticlePermalink

The post Barebones CSS for Fluid Images appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

You want…

Css Tricks - Wed, 02/17/2021 - 11:50am

I’ve been enjoying these little “You want…” style posts. Post titles like that are a little more… forceful for my normal taste, but I like the spirit of sharing a best practice that perhaps isn’t well-known-enough.

Got an idea along these lines? You should blog it! Here or elsewhere.

The post You want… appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Things You Can Do With CSS Today

Css Tricks - Wed, 02/17/2021 - 11:20am

Some nice coverage from Andy about CSS things that are truly new. If you haven’t looked at new things in CSS in, say, a year, I’d bet pretty much all of this will be new to you. A lot of it is cutting-edge enough that you might not be able to get it into projects immediately, but that gap is getting pretty short these days. Rough guess, I’d say a year and we’ll be using all this stuff without much thought.

  • Masonry layout (e.g. grid-template-rows: masonry;) which I think was a terribly clever way to approach this long-awaited feature..
  • The :is selector which makes certain selectors way less obnoxious to write (e.g. article :is(h1, h2, h3):not(:first-child)). This makes me wonder why we never had such a thing in Sass that helped with that.
  • Functions like min(), max(), and clamp(), and I think the best practices and clever uses for them are just shaking out. Fluid type is the most obvious and useful example, but only scratches the surface.
  • Units like ch and ex. I really like max-width: 70ch;. There’s a general bit of typography advice that line length should be between 45 and 75 characters long (I made a bookmarklet to test it once), so rather than forcing that with some abstractly related width, you use the actual width of characters as the width.
  • Fancy text-decoration (e.g. text-decoration-thickness: 0.5rem;) meaning we can thicken up an underline without giving up the usefulness that is text-decoration-skip-ink like we would if we had to resort to a border.)

Direct Link to ArticlePermalink

The post Things You Can Do With CSS Today appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS Switch-Case Conditions

Css Tricks - Wed, 02/17/2021 - 6:05am

CSS is yet to have a switch rule or conditional if, aside from the specific nature of @media queries and some deep trickery with CSS custom properties. Let’s have a look at why it would be useful if we did, and look at a trick that is usable today for pulling it off.

Recent chatter about the possibility

While none of these things are usable today, there has been a good amount of chat about the concept of generic conditional CSS just in the last year:

So, yes. The demand for conditional CSS is there.

Imagine why conditional CSS would be useful

Perhaps a visual change after a certain amount of scrolling. A visual change after a numeric input is within a certain range. A component with a handful of states.

There is a whole genre of extremely popular JavaScript libraries for UI (e.g. React, Vue, etc.) that are essentially for building UI based on state. Clearly this is a developer need. If we could move that state-based styling to CSS, that’s all the less JavaScript we might need — and maybe a better separation of concerns.

A common theme

We already have custom properties in CSS, and we could base state-change logic on them, changing a block of styles as a side effect of the custom property changing to certain values.

It’s true that we have mechanisms for changing blocks of styles already. We can change class through JavaScript, and that class can apply whatever we like in CSS. But that doesn’t mean state-based styling in CSS wouldn’t be useful. We don’t always have the ability or may not want to write any JavaScript for this, and instead change custom properties in other ways (e.g. media queries, HTML changes, etc). Doing it in CSS means helping separate business logic and visual style logic.

A trick! Using @keyframes for state

CSS @keyframes can be used to switch specific changes. Through the power of the animation property, a possibility opens up to select exactly which frame to show, and have it pause exactly on that frame, effectively mimicking a switch-case statement or state-based styles.

Let’s see see this in action by playing with the animation-delay property:

CodePen Embed Fallback

Here’s what’s happening in that Pen:

  • animation-delay: Negative delay values force a specific frame (or between) to take effect (positive values don’t work that way). We’ll use this trick to force states.
  • animation-play-state: paused: We’re not actually animating anything, so the animation will stay paused.
  • animation-duration: The actual duration doesn’t matter, it just needs one so there is a time span to hold the different keyframes. We’ll make it a value like 100.001s so that if we delay by 100s, the last keyframe will still work. The duration needs to be longer than the delay value.

The first range input modifies the animation-delay between a range of -100s and 0s.

A real-world use-case

Before we jump straight into the working example, it’s worth discussing this trick in more detail because there’s some nuances you ought to be aware of.

First off, the trick only works with numeric values. So, color values or strings because it’s strictly performing math.

Second, there’s the boolean trick. Consider a variable --value: 10 which can take any numeric value between 0 and 100. We want to apply color if the value is above 5. How do we know if the value is over or below 5? And even if we do know, how does that help actually help us?

--is-above-5: clamp(0, var(--value) - 5, 1) --value--is-above-5Result000 – 5 = -5, clamp() forces a value no less than 0202 – 5 = -3, clamp() forces a value no less than 0505 – 5 = 0717 – 5 = 2, clamp() forces a value no larger than 1

clamp() is like a smarter calc(), in that it allows us to strictly confine a computed value to range while declaring an ideal value. That range is all that is needed to achieve a boolean variable.

Write any math in the second parameter of the clamp() and that will either output 0 (or below) or 1 (or above). Make sure not to write any math that might result in a number between 0 and 1.

Here’s how that works out:

CodePen Embed Fallback

The range input’s only job is to “broadcast” its value by defining a values for --value, --min and --max, then modifying the --value using an oninput event. That is the most minimal thing that can be done get state-like behavior in CSS. No JavaScript needed.

Using CSS math functions, it is possible to infer the “completed” percentage of the progress bar from those same variables:

--completed: calc((var(--value) - var(--min) ) / (var(--max) - var(--min)) * 100);

Now, we know if the value is over a certain percentage, giving us yet another way to make changes by state:

--over-30: clamp(0, var(--completed) - 30, 1); --over-70: clamp(0, var(--completed) - 70, 1); /* ...and so on... */

OK, great, but how can we use this to select a specific keyframe? By using max() function:

--frame: max( calc(1 - var(--over-30)), var(--over-30) * 2, var(--over-70) * 3, var(--is-100) * 4 );

The thing with CSS booleans is that there are many ways to use them to achieve a certain goal, and one must get creative, finding a formula which is short and readable.

In the above formula, the booleans will “toggle” a frame number if the boolean has the value of 1. Since we are using a max function, the the largest toggled frame number will be the computed value of --frame.

Note that the color change has a slight transition. We could have done this with the background: currentColor; on the fill area, which inherits the color from the parent, but I chose to use CSS Houdini to illustrate the power of assigning transitions to CSS variables by declaring its type.

An example of a heavily-used CSS boolean trick can be viewed in the below Pen, which is a CSS-only component with lots of variables that allow a wide range of customization:

CodePen Embed Fallback

I am sure there are many other use cases for this little trick and am excited to see what else might be achieved by the creativity of the community.

The post CSS Switch-Case Conditions appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Use CSS Clamp to create a more flexible wrapper utility

Css Tricks - Wed, 02/17/2021 - 5:54am

I like Andy’s idea here:

.wrapper { width: clamp(16rem, 90vw, 70rem); margin-left: auto; margin-right: auto; padding-left: 1.5rem; padding-right: 1.5rem; }

Normally I’d just set a max-width there, but as Andy says:

This becomes a slight issue in mid-sized viewports, such as tablets in portrait mode, in long-form content, such as this article because contextually, the line-lengths feel very long.

So, on super large screens, you’ll get capped at 70rem (or whatever you think a good maximum is), and on small screens you’ll get full width, which is fine. But it’s those in-betweens that aren’t so great. I made a little demo to get a feel for it. This video makes it clear I think:

Direct Link to ArticlePermalink

The post Use CSS Clamp to create a more flexible wrapper utility appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Front of the Front / Back of the Front

Css Tricks - Tue, 02/16/2021 - 1:44pm

People really latched onto Brad’s framing. And for good reason. Front-end development has gotten so wide scoping that there are specialists inside of it. Two years ago, I cut it down the middle and now Brad is putting a point on that here, saying he has actual clients who have shifted their hiring strategy away from full-stack and toward these exact distinctions. Nice.

Brad shoots for a simple distinction:

A succinct way I’ve framed the split is that a front-of-the-front-end developer determines the look and feel of a button, while a back-of-the-front-end developer determines what happens when that button is clicked.

Part of me loves the clarity there. And part of me is like But! But! Wait! I’m a front-of-the-front kinda guy, but I totally deal with what happens on click. I’m a state updating machine over here. I’ll fire off that GraphQL mutation myself, thankyouverymuch. I friggin own that onClick.

And yet, I still don’t feel back-of-the-front at all. I can’t set up that GraphQL API or troubleshoot it. I don’t know what the security implications of the network request are. I don’t know if the query I wrote will be performant or not, or where to look at a graph to find out. I think I’d draw the line in a slightly different place than Brad, but he knows that. He’s flexible here:

The line between front-of-the-front-end and back-of-the-front-end can be fuzzy and varies greatly from developer to developer. It’s totally possible that one developer might be able to perform many tasks across the front-end spectrum. But it’s also worth pointing out that’s not super common.

That’s why the term “full-stack” isn’t my favorite. I bet an awful lot of developers have skillsets on both sides of the “great divide” which I think makes it feel like you’re full-stack when it’s more likely you’re cross-stack. Full-stack makes me feel like you’re deeply knowledgeable about literally everything across not only the front-end spectrum, but back-end too. Brad says that’s uncommon and I’ll up that and say it’s downright rare.

My main regret about trying to cut front-end development in half is that it’s too clean of a metaphor for a messy thing.

I live in Bend, Oregon, where outdoor hobbies are like the main thing. You don’t really go up and ask people if they are a summer sports person or a winter sports person because they are almost always both. But one person might be into snowshoeing, downhill skiing, and day hiking, where the next person might be into paddle boarding, nordic skiing, and mountain biking. So, I had this idea of Bend Name Tags where it lists all the outdoor hobbies and you write your name and then circle all the ones that you’re into.

It should almost be like that with front-end development. You write your name and then list out all the things you’re into. Me? I like HTML, CSS, JavaScript, Build Processes, Design, React, WordPress, with a dash of Accessibility, Performance, and Copywriting. What does that make me? Me.

Direct Link to ArticlePermalink

The post Front of the Front / Back of the Front appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Maximally optimizing image loading for the web in 2021

Css Tricks - Tue, 02/16/2021 - 11:16am

Malte Ubl’s list for:

8 image loading optimization techniques to minimize both the bandwidth used for loading images on the web and the CPU usage for image display.

  1. Fluid width images in CSS, not forgetting the height and width attributes in HTML so you get proper aspect-ratio on first render.
  2. Use content-visibility: auto;
  3. Send AVIF when you can.
  4. Use responsive images syntax.
  5. Set far-out expires headers on images and have a cache-busting strategy (like changing the file name).
  6. Use loading="lazy"
  7. Use decoding="async"
  8. Use inline CSS/SVG for a blurry placeholder.

Apparently, there is but one tool that does it all: eleventy-high-performance-blog.

My thoughts:

  • If you are lazy loading, do you really need to do the content-visibilty thing also? They seem very related.
  • Serving AVIF is usually good, but it seems less cut-and-dry than WebP was. You need to make sure your AVIF version is both better and smaller, which feels like a manual process right now. Update: I’m told that AVIF is actually more-reliably smaller (than JPG at least) than WebP is on the whole. I still think it’s worth being careful. This makes me want to outsource format choice to cloud providers that serve in the smallest format they can produce even moreso.
  • The decoding thing seems weird. I’ll totally use it if it’s a free perf win, but if it’s always a good idea, shouldn’t the browser just always do it?
  • I’m not super convinced blurry placeholders are in the same category of necessary as the rest of this stuff. Feels like a trend.

Direct Link to ArticlePermalink

The post Maximally optimizing image loading for the web in 2021 appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

The web didn’t change; you did

Css Tricks - Tue, 02/16/2021 - 6:01am

I love this piece from Remy Sharp where he argues that the web didn’t get more complicated over the last 20 years, despite what we might think:

Web development did not change. Web development grew. There are more options now, not different options.

Browsers have become more capable and still work with web pages built over 20 years ago.

[…] The web really didn’t change. It really didn’t become complex. The web development process is not one single path. There is simply more choice and more options.

Remy argues that the web is only really as complex as we make it and, when we choose an enormous framework for a small problem, it’s us that’s choosing the complexity. We really don’t have to build a website with the latest and greatest tools if we’re familiar with the old stuff and there’s no shame in using float over flexbox, if that works for you.

There’s a lot of ego in web design, and there’s a lot of folks out there bashing others for using the “incorrect” tools. But here’s the secret when it comes to making website: there are no perfect tools, and there’s no perfect way to build a website. That sucks, but it’s also exciting because we get to figure it all out; nothing is set in stone.

For example: I use Sass all the time for side projects and I know for a fact that a lotta folks would scoff at that. There’s emotion and Tachyons! There’s plain CSS! There’s PostCSS! But hey: I like Sass for a few things. I like the power it gives me and I like that I’m familiar with it. That doesn’t stop me from reaching for emotion in my day job or experimenting with something new when it comes along.

But old tech isn’t bad just because it’s old. And new tech isn’t good just because it’s new. You can see that sentiment playing out in the comment thread of Chris’ “Front-End Dissatisfaction (and Backing Off)” post.

Direct Link to ArticlePermalink

The post The web didn’t change; you did appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Using AbortController as an Alternative for Removing Event Listeners

Css Tricks - Mon, 02/15/2021 - 11:48am

The idea of an “abortable” fetch came to life in 2017 when AbortController was released. That gives us a way to bail on an API request initiated by fetch() — even multiple calls — whenever we want.

Here’s a super simple example using AbortController to cancel a fetch() request:

const controller = new AbortController(); const res = fetch('/', { signal: controller.signal }); controller.abort(); console.log(res); // => Promise(rejected): "DOMException: The user aborted a request"

You can really see its value when used for a modern interface of setTimeout. This way, making a fetch timeout after, say 10 seconds, is pretty straightforward:

function timeout(duration, signal) { return new Promise((resolve, reject) => { const handle = setTimeout(resolve, duration); signal?.addEventListener('abort', e => { clearTimeout(handle); reject(new Error('aborted')); }); }); } // Usage const controller = new AbortController(); const promise = timeout(10000, controller.signal); controller.abort(); console.log(promise); // => Promise(rejected): "Error: aborted"

But the big news is that addEventListener now accepts an Abort Signal as of Chrome 88. What’s cool about that? It can be used as an alternate of removeEventListener:

const controller = new AbortController(); eventTarget.addEventListener('event-type', handler, { signal: controller.signal }); controller.abort();

What’s even cooler than that? Well, because AbortController is capable of aborting multiple cancelable requests at once, it streamlines the process of removing multiple listeners in one fell swoop. I’ve already found it particularly useful for drag and drop.

Here’s how I would have written a drag and drop script without AbortController, relying two removeEventListener instances to wipe out two different events:

// With removeEventListener el.addEventListener('mousedown', e => { if (e.buttons !== 1) return; const onMousemove = e => { if (e.buttons !== 1) return; /* work */ } const onMouseup = e => { if (e.buttons & 1) return; window.removeEventListener('mousemove', onMousemove); window.removeEventListener('mouseup', onMouseup); } window.addEventListener('mousemove', onMousemove); window.addEventListener('mouseup', onMouseup); // Can’t use `once: true` here because we want to remove the event only when primary button is up });

With the latest update, addEventListener accepts the signal property as its second argument, allowing us to call abort() once to stop all event listeners when they’re no longer needed:

// With AbortController el.addEventListener('mousedown', e => { if (e.buttons !== 1) return; const controller = new AbortController(); window.addEventListener('mousemove', e => { if (e.buttons !== 1) return; /* work */ }, { signal: controller.signal }); window.addEventListener('mouseup', e => { if (e.buttons & 1) return; controller.abort(); }, { signal: controller.signal }); }); CodePen Embed Fallback

Again, Chrome 88 is currently the only place where addEventListener officially accepts an AbortSignal. While other major browsers, including Firefox and Safari, support AbortController, integrating its signal with addEventListener is a no go at the moment… and there are no signals (pun sorta intended) that they plan to work on it. That said, a polyfill is available.

The post Using AbortController as an Alternative for Removing Event Listeners appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Beautiful accessibility with Floating Focus

Css Tricks - Mon, 02/15/2021 - 5:38am

Imagine if your :focus styles animated from element to element as you tab through a site. Like the focus ring up and flew across the page to the next element. The spirit of it is similar to smooth scrolling: it’s easier to understand what is happening when movement accompanies the change¹. Rather than scrolling (or focus change) being an instant jump, movement guides you to the new location.

Guido Bouman thought this would be good for accessibility and looked at some options (e.g. Flying Focus) but ultimately created their own, Floating Focus:

After this exploration we had a good idea of what a good focus state needed. It needs to have a high contrast but not impair readability of the underlying components. It has to guide the user to the next focus target with a form of transition. And it only needs to show for users benefitting from the focus outline.

We’ve covered a similar thing before in 2019 when Maurice Mahan FocusOverlay.

Here’s what I wrote about it back then:

  • It’s a neat effect.
  • I can imagine it being an accessibility win since, while the page will scroll to make sure the next focused element is visible, it doesn’t otherwise help you see where that focus has gone. Movement that directs attention toward the next focused element may help make it more clear.
  • I can imagine it being harmful to accessibility in that it is motion that isn’t usually there and could be surprising and possibly offputting.
  • If it “just works” on all my focusable elements, that’s cool, but I see there are data attributes for controlling the behavior. If I find myself needing to sprinkle behavior control all over my templates to accommodate this specific library, I’d probably be less into it.

In that article, I covered a conditional loading idea for not loading this if prefers-reduced-motion was set to reduce. These days, you might do a conditional ES Modules import.

Don’t take any of this as advice that this movement-based focus stuff is 100% good for accessibility. I don’t feel qualified to make that determination. It is interesting though.

  1. This reminds me of “transitional interfaces” as well. Movement can really help make clear what is happening in a UI.

Direct Link to ArticlePermalink

The post Beautiful accessibility with Floating Focus appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Front-End Dissatisfaction (and Backing Off)

Css Tricks - Sat, 02/13/2021 - 8:21am

Asko Nõmm reached a breaking point with front end:

I want to have a personal life and not have to spend my nights reading up on some new flavour of *.js in fear that if I don’t I would soon be made irrelevant. I don’t want to learn nor use a million different tools. I don’t want to know a bit about everything and a lot about nothing.

Thus, I don’t want to do front-end development anymore. The joy is gone.

They literally spun up this blog to say that, but money-where-mouth-is:

I’ve given in my resignation at my current place of employment and will be seeking an exclusively back-end role for my next adventure

I have some doubts that back end is 100% better in regards to technology churn, but fair enough, I don’t hear about it as much. Front-end dissatisfaction is awfully high. I don’t go a day without hearing someone complain broadly about the state of front end.

Remy Sharp addressed this in The web didn’t change; you did:

If you didn’t gather off the bat from the title, the problem with developing front end projects isn’t that it’s harder or more complicated, it’s that you made it harder and more complicated.

Minor pushback there: a lot of people don’t get any choice in the technologies they are tasked with.

Remy’s point is that literally any simplicity that you hold nostalgia for on the web is still there and there is nothing stopping you from using it. Other than, ya know, if your client or boss prevents that.

Marc (last name appears intentionally not-on-the-internet) says that just HTML is a perfectly fine building tool:

Despite increasing leniency on frameworks being the only way to build for the web, hand-written HTML never disappeared and I feel is still a perfectly suitable way to build a personal website.

Remember Steren Giannini said recently they build websites with HTML alone and zero build process. And Terence Eden praised HTML for its unreasonable effectiveness:

Are you developing public services? Or a system that people might access when they’re in desperate need of help? Plain HTML works. A small bit of simple CSS will make look decent. JavaScript is probably unnecessary – but can be used to progressively enhance stuff. Add alt text to images so people paying per MB can understand what the images are for (and, you know, accessibility).

It’s nice to think that you can build an important website, avoid any sort of wild complexity, and have it do its job without any harm, with HTML.

Personally, I don’t harbor any ill will toward the front-end ecosystem at the moment. I’m aware that I can step backward from complexity if I have to, and that I can lean into complexity when it buys me things (speed, features, DX, etc) and know what it costs me (and users) and why.

The post Front-End Dissatisfaction (and Backing Off) appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Weekly Platform News: WebKit autofill, Using Cursor Pointer, Delaying Autoplay Videos

Css Tricks - Fri, 02/12/2021 - 11:53am

In this week’s roundup, WebKit’s prefixed autofill becomes a standard, the pointer cursor is for more than just links, and browsers are jumping on board to delay videos set to autoplay until they’re in view… plus more! Let’s jump right into it.

CSS ::-webkit-autofill has become a standard feature

Chrome, Safari, and pretty much every other modern web browser except Firefox (more on that later) have supported the CSS :-webkit-autofill pseudo-class for many years. This selector matches form fields that have been autofilled by the browser. Websites can use this feature to style autofilled fields in CSS (with some limitations) and detect such fields in JavaScript.

let autofilled = document.querySelectorAll(":-webkit-autofill");

There currently does not exist a standard autocomplete or autofill event that would fire when the browser autofills a form field, but you can listen to the input event on the web form and then check if any of its fields match the :-webkit-autofill selector.

The HTML Standard has now standardized this feature by adding :autofill (and :-webkit-autofill as an alias) to the list of pseudo-classes that match HTML elements. This pseudo-class will also be added to the CSS Selectors module.

The :autofill and :-webkit-autofill pseudo-classes must match <input> elements that have been autofilled by the user agent. These pseudo-classes must stop matching if the user edits the autofilled field.

Following standardization, both pseudo-classes have been implemented in Firefox and are expected to ship in Firefox 86 later this month.

You can use CSS Grid to define spacing in buttons and links

In the article “Let’s Bring Spacer GIFs Back!” Josh W. Comeau argues for using a “spacer” <span> element instead of a simple CSS margin to define the spacing between the icon and text of a button component.

In our home-button example, should the margin go on the back-arrow, or the text? It doesn’t feel to me like either element should “own” the space. It’s a distinct layout concern.

CSS Grid is an alternative to such spacer elements. For example, the “Link to issue” link in CSS-Tricks’s newsletter section contains two non-breaking spaces (&nbsp;) to increase the spacing between the emoji character and text, but the link could instead be turned into a simple grid layout to gain finer control over the spacing via the gap property.

Websites agree that the pointer cursor is not just for links

The CSS Basic User Interface module defines the CSS cursor property, which allows websites to change the type of cursor that is displayed when the user hovers specific elements. The specification has the following to say about the property’s pointer value:

The cursor is a pointer that indicates a link. … User agents must apply cursor: pointer to hyperlinks. … Authors should use pointer on links and may use on other interactive elements.

Accordingly, browsers display the pointer cursor (rendered as a hand) on links and the default cursor (rendered as an arrow) on buttons. However, most websites (including Wikipedia) don’t agree with this default style and apply cursor: pointer to other interactive elements, such as buttons and checkboxes, as well.

Another interactive element for which it makes sense to use the pointer cursor is the <summary> element (the “toggle button” for opening and closing the parent <details> element).

CodePen Embed Fallback Browsers delay autoplay until the video comes into view

Compared to modern video formats, animated GIF images are up to “twice as expensive in energy use.” For that reason, browsers have relaxed their video autoplay policies (some time ago) to encourage websites to switch from GIFs to silent or muted videos.

<!-- a basic re-implementation of a GIF using <video> --> <video autoplay loop muted playsinline src="meme.mp4"></video>

If you’re using <video muted autoplay>, don’t worry about pausing such videos when they’re no longer visible in the viewport (e.g., using an Intersection Observer). All major browsers (except Firefox) already perform this optimization by default:

<video autoplay> elements will only begin playing when visible on-screen such as when they are scrolled into the viewport, made visible through CSS, and inserted into the DOM.

CodePen Embed Fallback

(via Zach Leatherman)

Chrome introduces three new @font-face descriptors

Different browsers and operating systems sometimes use different font metrics even when rendering the same font. These differences affect the vertical position of text, which is especially noticeable on large headings.

Similarly, the different font metrics of a web font and its fallback font can cause a layout shift when the fonts are swapped during page load.

To help websites avoid layout shift and create interoperable text layouts, Chrome recently added the following three new CSS @font-face descriptors for overriding the font’s default metrics:

  • ascent-override (ascent is the height above the baseline)
  • descent-override (descent is the depth below the baseline)
  • line-gap-override
@font-face { font-family: Roboto; /* Merriweather Sans has 125.875px ascent * and 35px descent at 128px font size. */ ascent-override: calc(125.875 / 128 * 100%); descent-override: calc(35 / 128 * 100%); src: local(Roboto-Regular); }

The following video shows how overriding the ascent and descent metrics of the fallback font (Roboto) to match the same metrics of the web font (Merriweather Sans) can avoid layout shift when swapping between these two fonts.

The post Weekly Platform News: WebKit autofill, Using Cursor Pointer, Delaying Autoplay Videos appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Don’t put pointer-events: none on form labels

Css Tricks - Fri, 02/12/2021 - 10:03am

Bruce Lawson with the tip of the day, warning against the use of pointer-events: none on forms labels. We know that pointer-events is used to change how elements respond to click, tap, hover, and active states. But it apparently borks form labels, squashing their active hit target size to something small and tough to interact with. Bruce includes examples in his post.

That’s not the striking part of the post though. It’s that the issue was pinned to an implementation of Material Design’s floating labels component. Bruce fortunately had pointer events expert Patrick Lauke’s ear, who pointed (get it?) out the issue.

aha, now i remember when i first saw a few weeks ago – testing something based on material design for web https://t.co/YkEKXkU0To pic.twitter.com/31S74X1i4R

— patrick h. lauke #toryScum #clapForFlagWankers (@patrick_h_lauke) February 5, 2021

That isn’t a dig at frameworks. It’s just the reality of things. Front-end developers gotta be aware, and that includes awareness of third-party code.

Direct Link to ArticlePermalink

The post Don’t put pointer-events: none on form labels appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Responsible Web Applications

Css Tricks - Fri, 02/12/2021 - 10:03am

Joy Heron bought a cool domain name and published an article there:

Luckily, with modern HTML and CSS, we can create responsive and accessible web apps with relative ease. In my years of doing software development, I have learned some HTML and CSS tips and tricks, and I want to present these in this post. This list is not exhaustive, but these are tried and true patterns that I frequently use in different projects.

Sure, it’s a collection of tips and tricks, but it’s a great one that covers modern best practices across HTML, CSS, and JavaScript. If someone asked me what they should read if they missed out on the last, say, three years of front-end and wanted to remind themselves of the important stuff, I’d send them this.

I like the casual use of a massive shape-outside in the header.

Direct Link to ArticlePermalink

The post Responsible Web Applications appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Reconciling Editor Experience and Developer Experience in the CMS

Css Tricks - Fri, 02/12/2021 - 5:56am

Components are great, aren’t they? They are these reusable sources of truth that you can use to build rock-solid front-ends without duplicating code.

You know what else is super cool? Headless content management! Headless content management system (CMS) products offer a content editing experience while freeing that content in the form of data that can be ported, well, to any API-consuming front-end UI. You can structure your content however you’d like (depending on the product), and pull that content into your front-end applications.

Using these two things together — a distributed CMS solution with component-based front-end applications — is a core tenet of the Jamstack.

But, while components and headless CMSs are great on their own, it can be difficult to get them to play nicely together. I‘m not saying it‘s difficult to hook one up to the other. In a lot of cases, it’s actually quite painless. But, to craft a system of components that is reusable and consistent, and to have that system maintain parity with a well-designed CMS experience is a difficult thing to achieve. It’s that win-win combo of being able to freely write content and then have that content structured into predictable components that makes headless content management so appealing.

Achieving parity between a CMS and front-end components

My favorite demonstrating this complexity is a simple component: a button. Let‘s say we’re working with React to build components and our button looks like this:

<Button to="/">Go Home</Button>

In the lovely land of React, that means the <Button> component has two props (i.e. properties, attributes, arguments, etc.) — to and children. children is a React thing that holds all the content within the opening and closing tags, which is “Go Home” in this case.)

If we’re going to enable users in the content editor to add buttons to the site, we want a system for them that makes it easy to understand how their actions in the CMS affect what appears on screen in the front-end app. But we also want our developer(s) to work productively with component properties that make sense to them and within the framework they’re working (i.e. React in our example).

How do we do that?

We could…

…use fields in the CMS that match the components’ properties, though I’ve had little success with this approach. to and children don‘t make much sense to content editors trying to build a button. Believe me, I‘ve tried. I‘ve tried with beginners and experienced editors alike. I‘ve tried helper text. It doesn’t matter. It’s confusing.

What makes more sense is using words editors are more likely to understand, like label or text for children and url for to.

&#x1f615; &#x1f913;

But then we’d be out of sync with our code.

Or what if we…

masked attributes in the CMS. Most headless CMS solutions enable you to have a different value for the label of the field than the name that is used when delivering content via an API.

We could label our fields Label and URL, but use children and to as the names. We could. But we probably shouldn’t. Remember what Ian Malcolm said?

On the surface, masking attributes makes sense. It’s a separation of concerns. The editors see something that makes them happy and productive, and the developers work with the names that make sense to them. I like it, but only in theory. In practice, it confuses developers. Debugging a content editor issue often requires digging through extra layers (i.e. time) to find the relationship between labels and field names.

Or why not …

…change the properties. Wouldn’t it be easier for developers to be flexible? They’re the ones designing the system, after all.

Yes, that’s true. But if you follow that rule exclusively, it’s inevitable that you’re going to run into some issue along the way. You’ll likely end up fighting against the framework, or props will just feel goofy.

In our example, using label and url as props for a button works totally fine for data that originates from the CMS. But that also means that any time our developers want to use a button within the code, it looks like this:

<Button label="Go Home" url="/" />

That may seem okay on the surface, but it significantly limits the power of the button. Let’s say I want to support some other feature, like adding an icon within the label. I’m going to need some additional logic or another property for it. If I would have used React’s children approach instead, it would have just worked (likely after some custom styling support).

Okay, so… what do we do?

Introducing transformers

The best approach I’ve found is to separately optimize the editor and developer experiences. Craft a CMS experience that is catered to the editors. Build a codebase that is easy for developers to navigate, understand, and enhance.

The result is that the two experiences will not be in parity with one another. We need some set of utilities to transform the data from the CMS structure into something that can be used by the front-end, regardless of the framework and tooling you’re using.

I call these utilities transformers. (Aren’t I so good at naming things!?) Transformers are responsible for consuming data from your CMS and transforming it into a shape that can be easily consumed by your components.

While I‘ve found that transforming data is the smoothest means to get great experiences in both the CMS and the codebase, I don‘t have an obvious solution for how (or perhaps where) those transformations should happen. I‘ve used three different approaches, all of which have their pros and cons. Let’s take a look at them.

1. Alongside components

One approach is to put transformers right alongside the components they are serving. This is the approach I typically take in organizing component-based projects — to keep related files close to one another.

That means that I often have a directory for every component with a predictable set of files. The index.js acts as the controller for the component. It is responsible for importing and exporting all other relevant files. That makes it trivial to wrap the component with some logic-based behavior. In other words, it could transform properties of the component before rendering it. Here’s what that might look like for our button example:

import React from "react" import Component from "./component" import transform from "./transformer" const Button = props => <Component {...transform(props)} /> export default Button

The transform.js file might look like this:

export default input =&gt; { return { ...input, children: input.children || input.label, to: input.to || input.url } }

In this example, if to and children were properties sent to the component, it works just fine! But if label and url were used instead, they are transformed to children and to. That means the <Button> component (component.js) only has to worry about using children and to.

const Button = ({ children, to }) => <a href={to}>{children}</a>

I personally love this approach. It keeps the logic tightly coupled with the component. The biggest downside I‘ve found thus far is that it’s a large number of files and transforms, when the entire dataset for any given page could be transformed earlier in the stack, which would be…

2. At the top of the funnel

The data has to be pulled into the application via some mechanism. Developers use this mechanism to retrieve as much data for the current page or view as possible. Often, the fewer number of queries/requests a page is required to make, the better its performance.

In other words, that mechanism often exists near the top of the funnel (or stack), as opposed to each component pulling its own data in dynamically. (When that’s necessary, I use adapters.)

The mechanism that retrieves the page data could also be responsible for transforming all the data for the given page before it renders any of its components.

In theory, this is a better approach than the first one. It decreases the amount of work the browser has to do, which should improve the front-end performance. That means the server has to do more work, but that’s often a better choice.

In practice, though, this is a lot of work. Data structures can be big, complex, and interwoven. It can take a heck of a lot of work to transform everything into the right format at the top of the funnel, and then pass the transformed data down to components. It’s also more difficult to test because of the potential complexity and variation of the giant data blob retrieved at the top of the stack. With the first approach, testing the transformer logic for the button is trivial. With this approach, you’d want to account for transforming button data anywhere that it might appear in the retrieved data object.

But, if you can pull it off, this is generally the better approach.

3. The middleman engine

The third and final (and magical) approach is to do all this work somewhere else. In this case, we could build an engine (i.e. a small application) that would do the transformations for us, and then make the content available for the application to consume.

This is likely even more work than the second approach. And it has added cost and maintenance in running an additional application, which takes more effort to ensure it is rock solid.

The major upside to this approach is that we could build this as an abstracted engine. In other words, any time we bring in data to any front-end application, it goes through this middleman engine. That means if we have two projects that use the same CMS or data source, our work is cut down significantly for the second project.

If you aren‘t doing any of this today and want to start, my advice is to treat these approaches like stepping stones. They grow in complexity and maintenance and power as the application grows. Start with the first approach and see how far that gets you. Then, if you feel like you could benefit from a jump to the second, do it! And if you’re feeling like living dangerously, go for the third!

In the end, what matters most is crafting an experience that both your editors and your developers understand and enjoy. If you can do that, you win!

The post Reconciling Editor Experience and Developer Experience in the CMS appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

The Devil’s Albatross

Css Tricks - Fri, 02/12/2021 - 5:54am

Nils Binder talks about a technique for spacing between two elements. Picture a header on a large screen with a logo in the upper left and nav in the upper right. Then a small screen, when they can no longer be on the same “row” and need to wrap, they don’t just wrap but are centered.

A video explains better:

My mind goes: I’d just find the exact pixel value for the breakpoint I want this to happen and then write a media query that re-styles things to do that.

But… media queries are only for the entire browser window width. While that probably works in a case like this because it’s a full-site kinda concern, I get the desire to not have to write media queries. Nils’ idea borrow concepts from Heydon’s Holy Albatross to make this work without any media queries at all. So, you could use this on a smaller-scope component where you need to adjust the breaking point at a certain size that has nothing to do with the size of the browser window.

CodePen Embed Fallback

Direct Link to ArticlePermalink

The post The Devil’s Albatross appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Syndicate content
©2003 - Present Akamai Design & Development.