Full Width [alt+shift+f] Shortcuts [alt+shift+k]
Sign Up [alt+shift+s] Log In [alt+shift+l]
54
I recently stumbled on a super cool, well-executed hover effect from the clerk.com website where a bloom of tiny pixels light up, their glow staggering from the center to the edges of its container. With some available free time over this Thanksgiving break, I hacked together my own version of a pixel canvas background shimmer. It quickly evolved into a pixel-canvas Web Component that can be enjoyed in the demo below. The component script and demo code have also been pushed up to a GitHub repo. Open CodePen demo Usage Include the component script and then insert a pixel-canvas custom element inside the container it should fill. <script type="module" src="pixel-canvas.js"></script> <div class="container"> <pixel-canvas></pixel-canvas> <!-- other elements --> </div> The pixel-canvas stretches to the edges of the parent container. When the parent is hovered, glimmering pixel fun ensues. Options The custom element has a few optional attributes available to customize the...
4 months ago

Improve your reading experience

Logged in users get linked directly to articles resulting in a better reading experience. Please login for free, it takes less than 1 minute.

More from Ryan Mulligan

Blog Questions Challenge

Hey there. It has been a minute since my last post. I was semi-recently tagged by Zach Leatherman to (optionally) participate in this year's Blog Questions Challenge. I had planned on doing it then. But life really hit hard as we entered this year and it has not let up. Energy dedicated to my personal webspace has been non-existent. I am tired. Hopefully this post can help shake off some of the rust, bring me back to writing and sharing with you lovely folks. I won't be tagging anyone to do this challenge. However, if you're inspired to write your own after reading mine, I'd love for you to share it with me. Why did you start blogging in the first place? Blogging has always been a part of my web experience. Earliest I can remember is building my band a GeoCities website back in high school. I'd share short passages about new song ideas, how last night's show went, stuff like that. I also briefly had a Xanga blog running. My memory is totally faded on what exactly I wrote in there—I'm not eager to dig up high school feelings either—but fairly certain all of those entries are just lost digital history. Having an "online journal" was such a fresh idea at the time. Sharing felt more natural and real before the social media platforms took over. [blows raspberry] I've completely dated myself and probably sound like "old man yells at cloud" right now. Anyway, I pretty much stopped blogging for a while after high school. I turned my efforts back to pen on paper, keeping journals of lyrics, thoughts, and feelings mostly to myself. My dev-focused blogging that you may be familiar with really only spans the last decade, give or take a couple years. What platform are you using to manage your blog and why? At the moment and the forseeable future, I'm using 11ty. I published a short post about migrating to 11ty back in 2021. I still feel the same sentiments and still admire those same people. And many new community friends as well! Have you blogged on other platforms before? I've definitely used WordPress but I can't remember what the heck I was even blogging about during that time. Then I switched to just writing posts directly in HTML files and FTP'ing them up to some server somewhere. Pretty silly in retrospect, but boy did I feel alive. How do you write your posts? Always via laptop, never on my phone. I manage posts in markdown files, push them up to a GitHub repo and let that automatically redeploy my site on Netlify. Editing content is done in VSCode. I've debated switching to some lightweight CMS, connecting to Notion or Obsidian, but why introduce any more complexity and mess with what works fine for me? When do you feel most inspired to write? Typically I'll write up a post about something new I discovered while on my wild coding escapades, whether at work or in my free time. If I have trouble finding solutions to my particular problem on the world wide webs, I'm even more inclined to post about it. Most of my ideas are pursued on weekends, but I've had some early morning or late night weekday sessions. What I'm trying to say is that anytime is a good time for blogging. It's like pizza when it's on a bagel. Do you publish immediately after writing, or do you let it simmer a bit as a draft? It depends. If I had been writing for a long period of time, I find it best to take a breather before publishing. When I feel ready, I'll post and share with a small group for feedback, find grammatical errors. Then I eventually add it to whatever social channels feel right. Used to be Twitter, but straight up screw that garbage temple. I'll likely post on Bluesky, toot on Mastodon. Other times I'll slap a new post on this site and not share it on any socials. Let the RSS feeds do their magic. What's your favorite post on your blog? I don't know if I have a favorite. Can I love them all equally? Well, besides that CSS Marquee one. Damn that blog post for becoming so popular. Any future plans for your blog? Once things settle down in life, I think I'll be ready for a redesign. I had a blast building the current version inspired by Super Mario Wonder. Until then? More blogging. It won't be super soon, but I do have a few zesty article ideas percolating in this old, tired brain.

2 weeks ago 14 votes
Some Things About Keyframes

Whether you've barely scratched the surface of keyframe animations in CSS or fancy yourself as a seasoned pro, I suggest reading An Interactive Guide to Keyframe Animations. Josh (as always) does an impeccable deep dive that includes interactive demos for multi-step animations, loops, setting dynamic values, and more. This is a quick post pointing out some other minor particulars: Duplicate keyframe properties The order of keyframe rules Custom timing function (easing) values at specific keyframes Duplicate keyframe properties Imagine an "appearance" animation where an element slides down, scales up, and changes color. The starting 0% keyframe sets the element's y-axis position and scales down the size. The element glides down to its initial position for the full duration of the animation. About halfway through, the element's size is scaled back up and the background color changes. At first, we might be tempted to duplicate the background-color and scale properties in both 0% and 50% keyframe blocks. @keyframes animate { 0% { background-color: red; scale: 0.5; translate: 0 100%; } 50% { background-color: red; scale: 0.5; } 100% { background-color: green; scale: 1; translate: 0 0; } } Although this functions correctly, it requires us to manage the same property declarations in two locations. Instead of repeating, we can share them in a comma-separated ruleset. @keyframes animate { 0% { translate: 0 100%; } 0%, 50% { background-color: red; scale: 0.5; } 100% { background-color: green; scale: 1; translate: 0 0; } } Keyframe rules order Another semi-interesting qwirk is that we can rearrange the keyframe order. @keyframes animate { 0% { translate: 0 100%; } 100% { background-color: green; scale: 1; translate: 0 0; } /* Set and hold values until halfway through animation */ 0%, 50% { background-color: red; scale: 0.5; } } "Resolving Duplicates" from the MDN docs mentions that @keyframes rules don't cascade, which explains why this order still returns the expected animation. Customizing the order could be useful for grouping property changes within a @keyframes block as an animation becomes more complex. That same section of the MDN docs also points out that cascading does occur when multiple keyframes define the same percentage values. So, in the following @keyframes block, the second translate declaration overrides the first. @keyframes animate { to { translate: 0 100%; rotate: 1turn; } to { translate: 0 -100%; } } Keyframe-specific easing Under "Timing functions for keyframes" from the CSS Animations Level 1 spec, we discover that easing can be adjusted within a keyframe ruleset. A keyframe style rule may also declare the timing function that is to be used as the animation moves to the next keyframe. Toggle open the CSS panel in the ensuing CodePen demo and look for the @keyframes block. Inside one of the percentages, a custom easing is applied using the linear() CSS function to give each element some wobble as it lands. Open CodePen demo I think that looks quite nice! Adding keyframe-specific easing brings an extra layer of polish and vitality to our animations. One minor snag, though: We can't set a CSS variable as an animation-timing-function value. This unfortunately means we're unable to access shared custom easing values, say from a library or design system. :root { --easeOutCubic: cubic-bezier(0.33, 1, 0.68, 1); } @keyframes { 50% { animation-timing-function: var(--easeOutCubic); } } Helpful resources An Interactive Guide to Keyframe Animations @keyframes on MDN Easing Functions Cheat Sheet Linear easing generator The Path To Awesome CSS Easing With The linear() Function

3 months ago 75 votes
Scrolling Rails and Button Controls

Once again, here I am, hackin' away on horizontal scroll ideas. This iteration starts with a custom HTML tag. All the necessities for scroll overflow, scroll snapping, and row layout are handled with CSS. Then, as a little progressive enhancement treat, button elements are connected that scroll the previous or next set of items into view when clicked. Behold! The holy grail of scrolling rails... the scrolly-rail! CodePen demo GitHub repo Open CodePen demo I'm being quite facetious about the "holy grail" part, if that's not clear. 😅 This is an initial try on an idea I'll likely experiment more with. I've shared some thoughts on potential future improvements at the end of the post. With that out of the way, let's explore! The HTML Wrap any collection of items with the custom tag: <scrolly-rail> <ul> <li>1</li> <li>2</li> <li>3</li> <!-- and so on--> </ul> </scrolly-rail> The custom element script checks if the direct child within scrolly-rail is a wrapper element, which is true for the above HTML. While it is possible to have items without a wrapper element, if the custom element script runs and button controls are connected, sentinel elements are inserted at the start and end bounds of the scroll container. Wrapping the items makes controlling spacing between them much easier, avoiding any undesired gaps appearing due to these sentinels. We'll discover what the sentinels are for later in the post. The CSS Here are the main styles for the component: scrolly-rail { display: flex; overflow-x: auto; overscroll-behavior-x: contain; scroll-snap-type: x mandatory; @media (prefers-reduced-motion: no-preference) { scroll-behavior: smooth; } } When JavaScript is enabled, sentinel elements are inserted before and after the unordered list (<ul>) element in the HTML example above. Flexbox ensures that the sentinels are positioned on either side of the element. We'll find out why later in this post. Containing the overscroll behavior will prevent us accidentally triggering browser navigation when scrolling beyond either edge of the scrolly-rail container. scroll-snap-type enforces mandatory scroll snapping. Smooth scrolling behavior applies when items scroll into view on button click, or if interactive elements (links, buttons, etc.) inside items overflowing the visible scroll area are focused. Finally, scroll-snap-align: start should be set on the elements that will snap into place. This snap position aligns an item to the beginning of the scroll snap container. In the above HTML, this would apply to the <li> elements. scrolly-rail li { scroll-snap-align: start; } As mentioned earlier, this is everything our component needs for layout, inline scrolling, and scroll snapping. Note that the CodePen demo takes it a step further with some additional padding and margin styles (check out the demo CSS panel). However, if we'd like to wire up controls, we'll need to include the custom element script in our HTML. The custom element script Include the script file on the page. <script type="module" src="scrolly-rail.js"></script> To connect the previous/next button elements, give each an id value and add these values to the data-control-* attributes on the custom tag. <scrolly-rail data-control-previous="btn-previous" data-control-next="btn-next" > <!-- ... --> </scrolly-rail> <button id="btn-previous" class="btn-scrolly-rail">Previous</button> <button id="btn-next" class="btn-scrolly-rail">Next</button> Now clicking these buttons will pull the previous or next set of items into view. The amount of items to scroll by is based on how many are fully visible in the scroll container. For example, if we see three visible items, clicking the "next" button will scroll the subsequent three items into view. Observing inline scroll bounds Notice that the "previous" button element in the demo's top component. As we begin to scroll to the right, the button appears. Scrolling to the end causes the "next" button to disappear. Similarly, for the bottom component we can see either button fade when their respective scroll bound is reached. Recall the sentinels discussed earlier in this post? With a little help from the Intersection Observer API, the component watches for either sentinel intersecting the visible scroll area, indicating that we've reached a boundary. When this happens, a data-bound attribute is toggled on the respective button. This presents the opportunity to alter styles and provide additional visual feedback. .btn-scrolly-rail { /** default styles */ } .btn-scrolly-rail[data-bound] { /* styles to apply to button at boundary */ } Future improvements I'd love to hear from the community most specifically on improving the accessibility story here. Here are some general notes: I debated if button clicks should pass feedback to screen readers such as "Scrolled next three items into view" or "Reached scroll boundary" but felt unsure if that created unforeseen confusion. For items that contain interactive elements: If a new set of items scroll into view and a user tabs into the item list, should the initial focusable element start at the snap target? This could pair well with navigating the list using keyboard arrow keys. Is it worth authoring intersecting sentinel "enter/leave" events that we can listen for? Something like: Scroll bound reached? Do a thing. Leaving scroll bound? Revert the thing we just did or do another thing. Side note: prevent these events from firing when the component script initializes. How might this code get refactored once scroll snap events are widely available? I imagine we could check for when the first or last element becomes the snap target to handle toggling data-bound attributes. Then we can remove Intersection Observer functionality. And if any folks have other scroll component solutions to share, please reach out or open an issue on the repo.

3 months ago 79 votes
The Shape of Runs to Come

Over the last few months or so, I have been fairly consistent with getting outside for Sunday morning runs. A series of lower body issues had prevented me from doing so for many years, but it was an exercise I had enjoyed back then. It took time to rebuild that habit and muscle but I finally bested the behavior of doing so begrudgingly. Back in the day (what a weird phrase to say, how old am I?) I would purchase digital copies of full albums. I'd use my run time to digest the songs in the order the artist intended. Admittedly, I've become a lazy listener now, relying on streaming services to surface playlists that I mindlessly select to get going. I want to be better than that, but that's a story for another time. These days, my mood for music on runs can vary: Some sessions I'll pop in headphones and throw on some tunes, other times I head out free of devices (besides a watch to track all those sweet, sweaty workout stats) and simply take in the city noise. Before I headed out for my journey this morning, a friend shared a track from an album of song covers in tribute to The Refused's The Shape Of Punk To Come. The original is a treasured classic, a staple LP from my younger years, and I can still remember the feeling of the first time it struck my ears. Its magic is reconjured every time I hear it. When that reverb-soaked feedback starts on Worms of the Senses / Faculties of the Skull, my heart rate begins to ascend. The anticipation builds, my entire body well aware of the explosion of sound imminent. As my run began, I wasn't sure if I had goosebumps from the morning chill or the wall of noise about to ensue. My legs were already pumping. I was fully present, listening intently, ready for the blast. The sound abruptly detonated sending me rocketing down the street towards the rising sun. My current running goal is 4-in-40, traversing four miles under forty minutes. I'm certainly no Prefontaine, but it's a fair enough objective for my age and ability. I'll typically finish my journey in that duration or slightly spill over the forty-minute mark. Today was different. Listening to The Shape Of Punk To Come sent me cruising an extra quarter mile beyond the four before my workout ended. The unstoppable energy from that album is truly pure runner's fuel. There's certainly some layer of nostalgia, my younger spirit awakened and reignited by thrashing guitars and frantic rhythms, but many elements and themes on this record were so innovative at the time it was released. New Noise is a prime example that executes the following feeling flawlessly: Build anticipation, increase the energy level, and then right as the song seems prepped to blast off, switch to something unexpected. In this case, the guitars drop out to make way for some syncopated celestial synths layered over a soft drum rhythm. The energy sits in a holding pattern, unsure whether it should burst or cool down, when suddenly— Can I scream?! Oh my goodness, yes. Yes you can. I quickly morphed into a runner decades younger. I had erupted, my entire being barreling full speed ahead. The midpoint of this track pulls out the same sequence of build up, drop off, and teasing just long enough before unleashing another loud burst of noise, driving to its explosive outro. As the song wraps up, "The New Beat!" is howled repeatedly to a cheering crowd that, I would imagine, had not been standing still. I definitely needed a long stretch after this run.

4 months ago 48 votes

More in design

Building as gardening

Although I've never had a garden

23 hours ago 2 votes
Kimball International Showroom by Eastlake Studio

The driving principle behind our design for Kimball’s new Chicago showroom is a shift from seeing the “showroom” as a...

21 hours ago 1 votes
Managing friction

I thought lately about what has changed in my life over the years. People change (even if they don't admit it). And there are always some sort of triggers that cause these changes. Some people start a habit of running, maybe; some start hanging out with friends who

2 days ago 3 votes
Krem by Camille Piot

Creating a skincare brand means crafting an experience down to the finest detail. The packaging: A sculptural bottle topped with...

2 days ago 2 votes
Discernment in the Digital Age

How elimination, curation, and optimization can help us see through the technological mirror. Technology functions as both mirror and lens — reflecting our self-image while simultaneously shaping how we see everything else. This metaphor of recursion, while perhaps obvious once stated, is one that most people instinctively resist. Why this resistance? I think it is because the observation is not only about a kind of recursion, but it is itself recursive. The contexts in which we discuss technology’s distorting effects tend to be highly technological — internet-based forums, messaging, social media, and the like. It’s difficult to clarify from within, isn’t it? When we try to analyze or critique a technology while using it to do so, it’s as if we’re critiquing the label from inside the bottle. And these days, the bottle is another apt metaphor; it often feels like technology is something we are trapped within. And that’s just at the surface — the discussion layer. It goes much deeper. It’s astounding to confront the reality that nearly all the means by which we see and understand ourselves are technological. So much of modern culture is in its artifacts, and the rest couldn’t be described without them. There have been oral traditions, of course, but once we started making things, they grew scarce. For a human in the twenty-first century, self awareness, cultural identification, and countless other aspects of existence are all, in some way or another, technological. It’s difficult to question the mirror’s image when we’ve never seen ourselves without it. The interfaces through which we perceive ourselves and interpret the world are so integrated into our experience that recognizing their presence, let alone their distorting effects, requires an almost impossible perspective shift. Almost impossible. Because of course it can be done. In fact, I think it’s a matter of small steps evenly distributed throughout a normal lifestyle. It’s not a matter of secret initiation or withdrawing from society, though I think it can sometimes feel that way. How, then, can one step outside the mirror’s view? I’ve found three categories of action particularly helpful: Elimination One option we always have is to simply not use a thing. I often think about how fascinating it is that to not use a particular technology in our era seems radical — truly counter-cultural. The more drastic rejecting any given technology seems, the better an example it is of how dependent we have become upon it. Imagine how difficult a person’s life would be today if they were to entirely reject the internet. There’s no law in our country against opting out of the internet, but the countless day-to-day dependencies upon the it nearly amount to a cumulative obligation to be connected to it. Nevertheless, a person could do it. Few would, but they could. This kind of “brute force” response to technology has become a YouTube genre — the “I Went 30 Days Without ____” video is quite popular. And this is obviously because of how much effort it requires to eliminate even comparatively minor technologies from one’s life. Not the entire internet, but just social media, or just streaming services, or just a particular device or app. Elimination isn’t easy, but I’m a fan of it. The Amish are often thought of as simply rejecting modernity, but that’s not an accurate description of what actually motivates their way of life. Religion plays a foundational role, of course, but each Amish community works together to decide upon many aspects of how they live, including what technologies they adopt. Their guiding principle is whether a thing or practice strengthens their community. And their decision is a collective one. I find that inspiring. When I reject a technology, I do so because I either don’t feel I need it or because I feel that it doesn’t help me live the way I want to live. It’s not forever, and it isn’t with judgement for anyone else but me. These are probably my most radical eliminations: most social media (I still reluctantly have a LinkedIn profile), streaming services (except YouTube), all “smart home” devices of any kind, smartwatches, and for the last decade and counting, laptops. Don’t @ me because you can’t ;) Curation What I have in mind here is curation of information, not of technologies. Since it is simply impossible to consume all information, we all curate in some way, whether we’re aware of it or not. For some, though, this might actually be a matter of what technologies they use — for example, if a person only uses Netflix, then they only see what Netflix shows them. That’s curation, but Netflix is doing the work. However, I think it’s a good exercise to do a bit more curation of one’s own. I believe that if curation is going to be beneficial, it must involve being intentional about one’s entire media diet — what information we consume, from which sources, how frequently, and why. This last part requires the additional work of discerning what motivates and funds various information sources. Few, if any, are truly neutral. The reality is that as information grows in volume, the challenge of creating useful filters for it increases to near impossibility. Information environments operated on algorithms filter information for you based upon all kinds of factors, some of which align with your preferences and many of which don’t. There are many ways to avoid this, they are all more inconvenient than a social media news feed, and it is imperative that more people make the effort to do them. They range from subscribing to carefully-chosen sources, to using specialized apps, feed readers, ad and tracking-blocking browsers and VPNs to control how information gets to you. I recommend all of that and a constant vigilance because, sadly, there is no filter that will only show you the true stuff. Optimization Finally, there’s optimization — the fine-tuning you can do to nearly anything and everything you use. I’ve become increasingly active in seeking out and adjusting even the most detailed of application and device settings, shaping my experiences to be quieter, more limited, and aligned with my intentions rather than the manufacturers’ defaults. I spent thirty minutes nearly redesigning my entire experience in Slack in ways I had never been aware were even possible until recently. It’s made a world of difference to me. Just the other day, I found a video that had several recommendations for altering default settings in Mac OS that have completely solved daily annoyances I have just tolerated for years. I am always adjusting the way I organize files, the apps I use, and the way I use them because I think optimization is always worthwhile. And if I can’t optimize it, I’m likely to eliminate it. None of these approaches offers perfect protection from technological mediation, but together they create meaningful space for more direct control over your experience. But perhaps most important is creating physical spaces that remain relatively untouched by digital technology. I often think back to long trips I took before the era of ubiquitous computing and connection. During a journey from Providence to Malaysia in 2004, I stowed my laptop and cell phone knowing they’d be useless to me during 24 hours of transit. There was no in-cabin wifi, no easy way to have downloaded movies to my machine in advance, no place to even plug anything in. I spent most of that trip looking out the window, counting minutes, and simply thinking — a kind of unoccupied time that has become nearly extinct since then. What makes technological discernment in the digital age particularly challenging is that we’re drowning in a pit of rope where the only escape is often another rope. Information technology is designed to be a nearly wraparound lens on reality; it often feels like the only way to keep using a thing is to use another thing that limits the first thing. People who know me well have probably heard me rant for years about phone cases — “why do I need a case for my case?!” These days, the sincere answer to many peoples’ app overwhelm is another app. It’s almost funny. And yet, I do remain enthusiastic about technology’s creative potential. The ability to shape our world by making new things is an incredible gift. But we’ve gone overboard, creating new technologies simply because we can, without a coherent idea of how they’ll shape the world. This makes us bystanders to what Kevin Kelly describes as “what technology wants” — the agenda inherent in digital technology that makes it far from neutral. What we ultimately seek isn’t escape from technology itself, but recovery of certain human experiences that technology tends to overwhelm: sustained attention, silence, direct observation, unstructured thought, and the sense of being fully present rather than partially elsewhere. The most valuable skill in our digital age isn’t technical proficiency but technological discernment — the wisdom to know when to engage, when to disconnect, and how to shape our tools to serve our deeper human needs rather than allowing ourselves to be shaped by them. “It does us no good to make fantastic progress if we do not know how to live with it.” – Thomas Merton

2 days ago 3 votes