Developer News

Vox Accessibility Guidelines

Css Tricks - Fri, 03/09/2018 - 10:05am

I remember seeing these accessibility guidelines from Vox a while ago but it’s still interesting to go over them again today and see if there’s anything missing from my own process when it comes to improving accessibility.

And there’s an awful lot to remember! Color contrast, alt-text, keyboard navigation, focus states, and ARIA attributes are only a small snippet of the total number of things we ought to be mindful of when designing websites and so this checklist is certainly helpful for giving us all a good nudge from time to time.

Plus, it's worth remembering that there are small tweaks we can make and ways to advocate for improved accessibility in our projects.

Direct Link to ArticlePermalink

The post Vox Accessibility Guidelines appeared first on CSS-Tricks.

Some Things About `alt` Text

Css Tricks - Fri, 03/09/2018 - 4:38am

I'm sure you know about alt text. It's the attribute on the image tag that has the important task of describing what that image is for someone who can't see it for any reason. Please use them.

I don't want to dimish the please use them message, but some interesting alt-text-related things have come up in my day-to-day lately that are related.

When you don't

Hidde de Vries wrote You don't always need alternative text recently:

But when an icon has a word next to it, for example ‘Log out’, the icon itself is decorative and does not need an alternative text:

<button type="button"><img src="close.svg" alt="" /> Close</button>

In this case we can leave the alt attribute empty, as otherwise a screenreader would announce ‘button - close close’.

I would think in a perfect world because that icon is entirely decorative that applying it via CSS would be ideal, but the point stands: if you have to use an image, alt text hurts more than it helps here.

Can we get it for free?

Computers are pretttttty smart these days. Perhaps they could look at our images and offer up descriptions without us manually having to type them.

&#x1f525; I think I used machine learning to be nice to people! In this proof of concept, I’m creating dynamic alt text for screenreaders with Azure’s Computer Vision API. &#x1f4ab;https://t.co/Y21AHbRT4Y pic.twitter.com/KDfPZ4Sue0

— Sarah Drasner (@sarah_edo) November 13, 2017

Sarah's demo uses a Computer Vision API to do that.

What about figcaption?

I hate to say it, but I'm not particularly good at writing alt text descriptions for images in blog posts right here on CSS-Tricks. It's a problem we need to fix with process changes. We do often use <figcaption> though to add text that's related to an image. The way that text is often crafted feels like alt text to me. It describes what's going on in the image.

I was asking around about this, and Zell Liew told me he does the same thing:

I actually have the same question. Most of my figcaptions are used to describe the image so readers understand what the image is about.

In my mind, I figured it would be worse to drop the exact copy from the figcaption into the alt text, as someone who was reading alt text would then essentially read the same description twice.

I also talked to Eric Bailey who had an interesting idea.

<figure> <img src="screenshot.png" alt="Screenshot of Chrome displaying a split view. On the left is a page full of image thumbnails comparing pre and post-optimization filesize. On the right is Chrome developer tools showing paint rasterize duration for the images. With a 6x CPU slowdown, the longest Paint Raster took 0.27ms, AKA 0.00027 seconds."> <figcaption aria-hidden="true"> With a 6x CPU slowdown, the longest Paint Raster took 0.27ms, AKA 0.00027 seconds. </figcaption> </figure>

This:

  • Preserves figure styling
  • Avoids nulling alt, which can be problematic for some screen readers
  • Keeps the description close to the content and communicates point to SR users
  • Communicates significant takeaway to visual readers without duplicating reading for SR users
  • Uses an aria property designed to be used outside of forms

I'd stress that he considered this just an idea and it hasn't been heavily vetted by the larger accessibility community. If there are any of you out there reading, what do you think?

Eric's demo used a more verbose alt text than the figcaption, but it seems like the pattern would be fine even if they were identical.

Little reminder: Twitter has alt text

But you need to enable the feature.

The post Some Things About `alt` Text appeared first on CSS-Tricks.

Screen Recording Utilities for macOS

Css Tricks - Thu, 03/08/2018 - 10:25am

I record quite a few short little videos. Sometimes for use demonstrating bugs or weirdnesses. Sometimes right here for the blog. A lot of times for Instagram or other social media.

Allow me to get SUPER NITPICKY about what I like.

  • Multiple formats. Sometimes you need a GIF. Sometimes you need an MP4. Sometimes you need both. It's ideal if the software can export as either or both.
  • Easily resizeable recording area. If you need to record the entire screen, fine, but I feel like that's the job for more full-blown screencasting apps. More often, I need to record a smaller bit of the screen. Ideally, I can drag over the portion I want, but the more control the better.
  • Aspect ratios and saved sizes. Speaking of control, it's likely I might want a square recording (like if it's going to Instagram) or I might want a 16:9, a common aspect ratio for TV's and web video. Ideally, the software helps me get there quickly.
  • Cursor/clicking, or not. Sometimes the point of a video is to demonstrate something, which might require showing the cursor and interactions like clicks. Ideally, that is available but turn-off-able.
  • Editing after recording. The chances of getting a perfect take are rare. More commonly, I'd like to adjust the start and end time of the recording. Since it's likely the GIF or video is meant to repeat, playing the recording as this is happening is ideal.
  • Configurable shortcuts. I'd ideally like to hit a keyboard command to fire up the app, select a recording area, and go.
  • Audio or no audio. It should be possible to record sound, clear if I am or not, and configurable.
  • Cost. This is just informational as it's typically a factor. Generally I like to pay for things as it can be a good indicator of quality and support. But as we all known, open source can be incredible, and incentivized companies can do well making free products, too.
  • Retains history. Maybe I need to re-cut it. Maybe I lost my export somehow. Maybe the app weirdly quit. Ideally, I'd like some history so I can go back to some older recordings and export another copy.
GIPHY Capture The green box there is the area of the screen GIPHY Capture records.

This is a great idea for a company like GIPHY to build, and they've done a fine job here. Best of all, I've watched it evolve over time to get more and more useful. As a cool bonus feature, you can add captions at specific points in the recording.

My main gripe is the two-window system. The green-box window has recording and history, then a second window for editing and exporting. That alone is no big deal, but when you are editing, you often want to be gone with the green box. But closing the green box means quitting the whole app.

Multiple formats GIF, MP4, or "Batch" which outputs a folder with both. Easily resizeable recording area Position and size the green box over the area you want to record. Aspect ratios and saved sizes No aspect ratios, but you can specifiy pixel width/height and it will save your recently used ones. A bit hidden, you have to click the pixel dimensions in the lower right to access it. Cursor/clicking Recording the cursor is on/off setting. If on, it adds a circle around the cursor when you click. Editing after recording Handles at the beginning and end of the timeline allow you to drag them inward to crop the clip. Very nicely handled (get it?) — I think this might be the best take on editing. File size control You can choose from a handful of options for both pixel size and frame rate to control size.

Not as fine-grained as you might want but likely fits most needs. Configurable shortcuts When the app is open, you can set a letter or number as a key command to start/stop recording. Audio or no audio No audio recording at all Cost Free Retains history I'm not sure how far back the history goes (it's a bit hard to navigate beyond what you can see) but the lower bar of the green recording window gives history access to the last few very easily. Batch exporting is a clever feature. Sometimes I really do need both types (GIF and video), and that need is likely to increase. Kap

I think Kap is my favorite one. At least it is today. It's quite polished, and also open source, perhaps as a bit of marketing for the agency it comes from.

Kap is at version 2.0 right now, and I quite like it. I had 1.0 and aborted pretty quickly. I can't remember why exactly, but it didn't measure up to other options. Version 2.0 is perhaps best-of-breed. I'm a fan of the fact that it's a menu bar app, so it is ready all the time instead of something I need to launch.

Its fancy bonus feature is installable export locations, like uploading to Cloudinary or S3.

The keyboard shortcut is one thing that (and this is weirdly unique to me) really bugs me. It actually keeps me from having it open all the time, because Command-Shift-5 is CodePen's command for re-running, which I use all the time. Configurability, please!

Multiple formats The most formats! GIF and MP4, but also WebM and APNG. Cool, but you can only export one at a time. Easily resizeable recording area Clicking the record button gives you little black-white dashed lines you position and size over the recording area. If GIPHY Capture's green screen is papa bear (too much), this is mama bear (too little). There is probably a just right baby bear in there somewhere. Aspect ratios and saved sizes Sizing is a first-class citizen here, giving you a dropdown for aspect ratio or controls for exact sizes (that it remembers). Cursor/clicking Under preferences, you can flip cursor recording on and off (and separately from click highlighting). Editing after recording Drag handles from the start or end inward to edit. File size control It's a big strange. There is an FPS control buried in settings to adjust the frame rate, but then on the editing screen before you export, you only get to pick between 30 and 15, so it's not clear what happens if you've adjusted it in settings to something other than those. Configurable shortcuts Weirdly, it's Command-Shift-5, which it doesn't tell you, allow you to turn off, or configure. Audio or no audio One click to turn on and off right before you record or after you record. Cost Free and open source. Retains history No history, but warns you before you close an editing window so you don't accidently lose recordings. LICEcap

Old school! LICEcap is the app that opened my eyes to the idea that these apps were even a thing. Perhaps the first of its kind.

Multiple formats GIF only Easily resizeable recording area The empty frame window might be the most clear UI out of all of them. Aspect ratios and saved sizes Neither, but it is easy to manually resize or type in pixel dimensions manually Cursor/clicking You decide if you want it right before you record. Editing after recording None File size control You can choose the FPS as you record, so you have good control, but it can't be changed after recording. So if it ends up too large or too small, you have to re-record. Configurable shortcuts None Audio or no audio As it's GIF only, there is no audio. Cost Free Retains history No Droplr Menu bar app

Droplr absolutely has the power to record quick screencasts, but it's much more limited than these others. What it does offer is a very quick way to get your screencasts up onto the web in a permanent and shareable way very quickly. If that's the most important thing to you, you'd be in good hands.

Multiple formats GIF or MOV Easily resizeable recording area Every time you record you have to drag over the area you want to record. Aspect ratios and saved sizes Easy to select a recording area, but it doesn't save sizes, tell you the dimensions of your selected area, or help with aspect ratios. Cursor/clicking Automatically includes cursor and click highlighting. Editing after recording None. You just choose GIF or MOV and it auto-uploads it. The ability to at least save locally before uploading would be nice. File size control None Configurable shortcuts You can pick a custom keyboard command for screencasts, along with different key commands for everythinge else Droplr does. Audio or no audio As you upload, it gives the impression that videos automatically include sound. But, you can turn off audio recording in preferences. Cost Freemium. If you need unlimited length screen recordings, it's $8.29/month. Retains history Yep, through the Droplr service, you'll have a complete history of all recordings. CloudApp

Like Droplr, CloudApp will help you record a screencast, but it's all about getting that screencast uploaded to their service so you can share it from there. That can be awfully handy, but also get in the way when you just want to work locally. You can set a preference to save the GIF and movie record locally, but it's a PRO feature.

Multiple formats GIF or MOV, which you pick before you record. Easily resizeable recording area Drag over the area you want. Aspect ratios and saved sizes Also like Droplr, you drag over the area you want, but it doesn't tell you the dimensions as you are doing it, allow to specify or adjust that size with numbers or help with aspect ratios. Cursor/clicking Option in preferences. Editing after recording No File size control In preferences, you can configure GIF FPS at 3, 6, or 12. Configurable shortcuts Lots of options for all the types of screenshots or recordings you can do. Audio or no audio Button to turn on or off (and select source) right before you record. Cost Freemium. Pro plans start at $8/month. Very heavy on the upgrade triggers. Retains history On their service. Quicktime Player

It's worth knowing that macOS has a built-in way to record the screen, and it's not half bad. Pop it open and do File > New Screen Recording and you have a decent little tool for video recordings.

Multiple formats Just video. Easily resizeable recording area Drag over the area you want to record (or just click to record the whole screen). Aspect ratios and saved sizes No help with saved sizes, specifying the size you want, previously used sizes, or aspect ratios. Cursor/clicking You automatically get the cursor, but no click highlighting. Editing after recording Command-T gives you a Trim dialog for editing the start and end points. File size control You can export the video in 4K, 1080p, 720p, etc. If the video isn't big enough, the unavailable options are grayed out. Configurable shortcuts Not configurable, but it does have default shortcuts for when the app is open and active. Audio or no audio Beside the red button that starts the recording there is a dropdown to select an audio source (or none). Cost Free, as in comes with macOS. Retains history Only if you save the files. It will prompt you to save before closing them. Gifox

Gifox was unknown to me before I started looking around for this post. It's pretty great! Very modern. Lots of options. Fairly priced. Plus a few pretty neat features.

Multiple formats Only GIF, which is unfortunate as it might be this app's only weakness. Easily resizeable recording area Drag to record an area (helps you with coordinates and sizing) or record specific windows (nice touch). Aspect ratios and saved sizes No aspect ratios, but it does allow you to lock the size so that subsequent recordings open up at exactly the same size. Cursor/clicking Option in settings. Editing after recording None File size control In settings you can control recording and playback FPS. Configurable shortcuts Very helpful with shortcuts. Little stuff like the spacebar to start/stop recording, but also a bunch of configurable ones in settings. Audio or no audio GIF only, so no audio. Cost Free version, but $4.99 for the licensed version which you'll probably want because it watermarks the recording otherwise. Retains history Automatically saves all recordings, so you can't lose them unless you trash them. Also has integrations with Dropbox, Google Drive, and Imgur so it can push directly there. Screenflow

Screenflow is really beefy screencasting software. All the stuff we've looked at so far is for little tiny quicky stuff. Screenflow is for long-form, edited, fancy screencasts. You can use it for little stuff, but it would be overkill and all the control would probably get in the way more than help. But if you need lots of control, it's fantastic.

Multiple formats Video is the primary target here, but the latest version does now have animated GIF export. Easily resizeable recording area Screenflow turns this on it's head. Generally, you record the entire screen, then during editing, you crop down to what you need. Newer versions let you scope down the recording area before you record, but the old paradigm is still there and probably a smart way to work in general. Aspect ratios and saved sizes Lots of control. It defaults to resizing as an aspect ratio when you crop after recording, but you can change it with hard pixel values if you wish, or choose from presets. Cursor/clicking Loads of control here. As you're editing, you can add action points that allow you to focus on the cursor with various effects, like graying out the rest of the screen. Editing after recording This is the main point of Screenflow. File size control Lots of exporting control for size, speed, and quality. Configurable shortcuts Massive set of configurable keyboard commands. Audio or no audio You choose what audio sources you want to record when you record. You can always remove those tracks during editing, or edit the audio just as you do the video. Cost Starts at $129.99. Retains history Only what you save. The Graveyard?
  • Screeny. Looks pretty nice, but also looks like it hasn't been touched in five years and I didn't wanna spend $14.99 when there seems to be a lot of good modern alternatives.
  • Recordit. Looks pretty similar to some of these others — notably CloudApp and Droplr — as it has a hosted service. But also sorta looks limited and abandoned.
  • GifGrabber. Looks pretty good and it's free! Just also feels a bit abandoned and only does GIF. This is the one that turned into GIPHY Capture.

The post Screen Recording Utilities for macOS appeared first on CSS-Tricks.

Creating a Parking Game With the HTML Drag and Drop API

Css Tricks - Thu, 03/08/2018 - 4:15am

Among the many JavaScript APIs added in HTML5 was Drag and Drop (we’ll refer to it as DnD in this article) which brought native DnD support to the browser, making it easier for developers to implement this interactive feature into applications. The amazing thing that happens when features become easier to implement is that people start making all kinds of silly, impractical things with it, like the one we’re making today: a parking game!

DnD requires only a few things to work:

  • Something to drag
  • Somewhere to drop
  • JavaScript event handlers on the target to tell the browser it can drop

We’re going to start by creating our draggables.

Dragging

Both <img> and <a>(with the href attribute set) elements are draggable by default. If you want to drag a different element, you'll need to set the draggable attribute to true.

We’ll start with the HTML that sets up the images for our four vehicles: fire truck, ambulance, car and bicycle.

<ul class="vehicles"> <li> <!-- Fire Truck --> <!-- <code>img<code> elements don't need a <code>draggable<code> attribute like other elements --> <img id="fire-truck" alt="fire truck" src="https://cdn.glitch.com/20f985bd-431d-4807-857b-e966e015c91b%2Ftruck-clip-art-fire-truck4.png?1519011787956"/> </li> <li> <!-- Ambulance --> <img id="ambulance" alt="ambulance" src="https://cdn.glitch.com/20f985bd-431d-4807-857b-e966e015c91b%2Fambulance5.png?1519011787610"> </li> <li> <!-- Car --> <img id="car" alt="car" src="https://cdn.glitch.com/20f985bd-431d-4807-857b-e966e015c91b%2Fcar-20clip-20art-1311497037_Vector_Clipart.png?1519011788408"> </li> <li> <!-- Bike --> <img id="bike" alt="bicycle" src="https://cdn.glitch.com/20f985bd-431d-4807-857b-e966e015c91b%2Fbicycle-20clip-20art-bicycle3.png?1519011787816"> </li> </ul>

Since images are draggable by default, you’ll see dragging any one of them creates a ghost image.

Just adding a draggable attribute to an element that’s not an image or link is really all you need to make an element draggable in most browsers. To make elements draggable in all browsers, you need to define some event handlers. They are also useful for adding extra functionality like a border if an element is being dragged around or a sound if it stops being dragged. For these, you’re going to need some drag event handlers, so let’s look at those.

Drag Events

There are three drag-related events you can listen for but we’re only going to use two: dragstart and dragend.

  • dragstart - Triggered as soon as we start dragging. This is where we can define the drag data and the drag effect.
  • dragend - Triggered when a draggable element is dropped. This event is generally fired right after the drop zone’s drop event.

We’ll cover what the drag data and the drag effect is shortly.

let dragged; // Keeps track of what's being dragged - we'll use this later! function onDragStart(event) { let target = event.target; if (target && target.nodeName === 'IMG') { // If target is an image dragged = target; event.dataTransfer.setData('text', target.id); event.dataTransfer.dropEffect = 'move'; // Make it half transparent when it's being dragged event.target.style.opacity = .3; } } function onDragEnd(event) { if (event.target && event.target.nodeName === 'IMG') { // Reset the transparency event.target.style.opacity = ''; // Reset opacity when dragging ends dragged = null; } } // Adding event listeners const vehicles = document.querySelector('.vehicles'); vehicles.addEventListener('dragstart', onDragStart); vehicles.addEventListener('dragend', onDragEnd);

There are a couple of things happening in this code:

  • We are defining the drag data. Each drag event has a property called dataTransfer that stores the event's data. You can use the setData(type, data) method to add a dragged item to the drag data. We’re storing the dragged image’s ID as type 'text' in line 7.
  • We’re storing the element being dragged in a global variable. I know, I know. Global is dangerous for scoping but here’s why we do it: although you can store the dragged item using setData, you can’t retrieve it using event.dataTransfer.getData() in all browsers (except Firefox) because the drag data is protected mode. You can read more about it here. I wanted to mention defining the drag data just so you know about it.
  • We’re setting the dropEffect to move. The dropEffect property is used to control the feedback the user is given during a drag and drop operation. For example, it changes which cursor the browser displays while dragging. There are three effects: copy, move and link.
    • copy - Indicates that the data being dragged will be copied from its source to the drop location.
    • move - Indicates that the data being dragged will be moved.
    • link - Indicates that some form of relationship will be created between the source and drop locations.

Now we have draggable vehicles but nowhere to drop them:

See the Pen 1 - Can you park here? by Omayeli Arenyeka (@yelly) on CodePen.

Dropping

By default, when you drag an element, only form elements such as <input> will be able to accept it as a drop. We’re going to contain our “dropzone” in a <section> element, so we need to add drop event handlers so it can accept drops just like a form element.

First, since it’s an empty element we’re going to need to set a width, height and background color on it so we can see it on screen.

These are the parameters we have available for drop events:

  • dragenter - Triggered at the moment a draggable item enters a droppable area. At least 50% of the draggable element has to be inside the drop zone.
  • dragover - The same as dragenter but it is called repeatedly while the draggable item is within the drop zone.
  • dragleave - Triggered once a draggable item has moved away from a drop zone.
  • drop - Triggered when the draggable item has been released and the drop area agrees to accept the drop.
function onDragOver(event) { // Prevent default to allow drop event.preventDefault(); } function onDragLeave(event) { event.target.style.background = ''; } function onDragEnter(event) { const target = event.target; if (target) { event.preventDefault(); // Set the dropEffect to move event.dataTransfer.dropEffect = 'move' target.style.background = '#1f904e'; } } function onDrop(event) { const target = event.target; if ( target) { target.style.backgroundColor = ''; event.preventDefault(); // Get the id of the target and add the moved element to the target's DOM dragged.parentNode.removeChild(dragged); dragged.style.opacity = ''; target.appendChild(dragged); } } const dropZone = document.querySelector('.drop-zone'); dropZone.addEventListener('drop', onDrop); dropZone.addEventListener('dragenter', onDragEnter); dropZone.addEventListener('dragleave', onDragLeave); dropZone.addEventListener('dragover', onDragOver);

If you’re wondering why we keep calling event.preventDefault() it’s because by default the browser assumes any target is not a valid drop target. This isn’t true all the time for all browsers but it’s better to be safe than sorry! Calling preventDefault() on the dragenter, dragover and drop events, informs the browser that the current target is a valid drop target.

Now, we have a simple drag and drop application!

See the Pen 2 - Can you park here? by Omayeli Arenyeka (@yelly) on CodePen.

It’s fun, but not quite as frustrating as parking. We have to create some rules to make that happen.

Rules and Validation

I came up with some random parking rules, and I’d encourage you to create some of your own. Parking signs usually have days and times you can park as well as what types of vehicles are allowed to park at that moment in time. When we were creating our draggable objects, we had four vehicles: an ambulance, a fire truck, a regular car and a bicycle. So, we’re going to create rules for them.

  1. Ambulance parking only: Monday through Friday, 9pm to 3am.
  2. Fire truck parking only: All day during the weekend.
  3. Regular car parking: Monday through Friday, 3am to 3pm.
  4. Bicycle parking: Monday through Friday, 3pm to 9pm.

Now, we translate these rules to code. We’re going to be using two libraries to handle time and ranges: Moment and Moment-range.

The scripts are already available in Codepen to add to any new demo, but if you are developing outside of Codepen you can copy or link them up from here:

<script defer src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.js"></script> <script defer src="https://cdnjs.cloudflare.com/ajax/libs/moment-range/3.1.1/moment-range.js"></script>

Then, we create an object to store all the parking rules.

window['moment-range'].extendMoment(moment); // The array of weekdays const weekdays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']; const parkingRules = { ambulance: { // The ambulance can only park on weekdays... days: weekdays, // ...from 9pm to 3am (the next day) times: createRange(moment().set('hour', 21), moment().add(1, 'day').set('hour', 3)) }, 'fire truck': { // The fire truck can obnly park on Saturdays and Sundays, but all day days: ['Saturday', 'Sunday'] }, car: { // The car can only park on weekdays... days: weekdays, // ...from 3am - 3pm (the same day) times: createRange(moment().set('hour', 3), moment().set('hour', 15)) }, bicycle: { // The car can only park on weekdays... days: weekdays, // ...from 3pm - 9pm (the same day) times: createRange(moment().set('hour', 15), moment().set('hour', 21)) } }; function createRange(start, end) { if (start && end) { return moment.range(start, end); } }

Each vehicle in the parkingRules object has a days property with an array of days it can park and a times property that is a time range. To get the current time using Moment, call moment(). To create a range using Moment-range, pass a start and end time to the moment.range function.

Now, in the onDragEnter and onDrop event handlers we defined earlier, we add some checks to make sure a vehicle can park. Our alt attribute on the img tag is storing the type of vehicle so we pass that to a canPark method which will return if the car can be parked. We also added visual cues (change in background) to tell the user whether a vehicle can be parked or not.

function onDragEnter(event) { const target = event.target; if (dragged && target) { const vehicleType = dragged.alt; // e.g bicycle, ambulance if (canPark(vehicleType)) { event.preventDefault(); // Set the dropEffect to move event.dataTransfer.dropEffect = 'move'; /* Change color to green to show it can be dropped /* target.style.background = '#1f904e'; } else { /* Change color to red to show it can't be dropped. Notice we * don't call event.preventDefault() here so the browser won't * allow a drop by default */ target.style.backgroundColor = '#d51c00'; } } } function onDrop(event) { const target = event.target; if (target) { const data = event.dataTransfer.getData('text'); const dragged = document.getElementById(data); const vehicleType = dragged.alt; target.style.backgroundColor = ''; if (canPark(vehicleType)) { event.preventDefault(); // Get the ID of the target and add the moved element to the target's DOM dragged.style.opacity = ''; target.appendChild(dragged); } } }

Then, we create the canPark method.

function getDay() { return moment().format('dddd'); // format as 'monday' not 1 } function getHours() { return moment().hour(); } function canPark(vehicle) { /* Check the time and the type of vehicle being dragged * to see if it can park at this time */ if (vehicle && parkingRules[vehicle]) { const rules = parkingRules[vehicle]; const validDays = rules.days; const validTimes = rules.times; const curDay = getDay(); if (validDays) { /* If the current day is included on the parking days for the vehicle * And if the current time is within the range */ return validDays.includes(curDay) && (validTimes ? validTimes.contains(moment()) : true); /* Moment.range has a contains function that checks * to see if your range contains a moment. https://github.com/rotaready/moment-range#contains */ } } return false; }

Now, only cars that are allowed to park can park. Lastly, we add the rules to the screen and style it.

Here’s the final result:

See the Pen 3 - Can you park here? by Omayeli Arenyeka (@yelly) on CodePen.

There are lots of ways this could be improved:

  • Auto-generate the HTML for the rules list from the parkingRules object!
  • Add some sound effects!
  • Add ability to drag back vehicles to original point without a page refresh.
  • All those pesky global variables.

But I’ll let you handle that.

If you’re interested in learning more about the DnD API and some critiques of it, here’s some good reading:

The post Creating a Parking Game With the HTML Drag and Drop API appeared first on CSS-Tricks.

?What do you think about headless CMS?

Css Tricks - Thu, 03/08/2018 - 4:14am

(This is a sponsored post.)

Headless CMS is the new kid on the technology block. Some say it’s the only way forward, while others call it a fad without a future. So we decided to conduct a study to see what people think about headless CMS and why they want to use it. Has headless got a future?

Share with us your opinion on the headless CMS and get a chance to win $50 Amazon gift card.

Start the survey

Direct Link to ArticlePermalink

The post ?What do you think about headless CMS? appeared first on CSS-Tricks.

What Houdini Means for Animating Transforms

Css Tricks - Wed, 03/07/2018 - 3:15am

I've been playing with CSS transforms for over five years and one thing that has always bugged me was that I couldn't animate the components of a transform chain individually. This article is going to explain the problem, the old workaround, the new magic Houdini solution and, finally, will offer you a feast of eye candy through better looking examples than those used to illustrate concepts.

The Problem

In order to better understand the issue at hand, let's consider the example of a box we move horizontally across the screen. This means one div as far as the HTML goes:

<div class="box"></div>

The CSS is also pretty straightforward. We give this box dimensions, a background and position it in the middle horizontally with a margin.

$d: 4em; .box { margin: .25*$d auto; width: $d; height: $d; background: #f90; }

See the Pen by thebabydino (@thebabydino) on CodePen.

Next, with the help of a translation along the x axis, we move it by half a viewport (50vw) to the left (in the negative direction of the x axis, the positive one being towards the right):

transform: translate(-50vw);

See the Pen by thebabydino (@thebabydino) on CodePen.

Now the left half of the box is outside the screen. Decreasing the absolute amount of translation by half its edge length puts it fully within the viewport while decreasing it by anything more, let's say a full edge length (which is $d or 100%—remember that % values in translate() functions are relative to the dimensions of the element being translated), makes it not even touch the left edge of the viewport anymore.

transform: translate(calc(-1*(50vw - 100%)));

See the Pen by thebabydino (@thebabydino) on CodePen.

This is going to be our initial animation position.

We then create a set of @keyframes to move the box to the symmetrical position with respect to the initial one with no translation and reference them when setting the animation:

$t: 1.5s; .box { /* same styles as before */ animation: move $t ease-in-out infinite alternate; } @keyframes move { to { transform: translate(calc(50vw - 100%)); } }

This all works as expected, giving us a box that moves from left to right and back:

See the Pen by thebabydino (@thebabydino) on CodePen.

But this is a pretty boring animation, so let's make it more interesting. Let's say we want the box to be scaled down to a factor of .1 when it's in the middle and have its normal size at the two ends. We could add one more keyframe:

50% { transform: scale(.1); }

The box now also scales (demo), but, since we've added an extra keyframe, the timing function is not applied for the whole animation anymore—just for the portions in between keyframes. This makes our translation slow in the middle (at 50%) as we now also have a keyframe there. So we need to tweak the timing function, both in the animation value and in the @keyframes. In our case, since we want to have an ease-in-out overall, we can split it into one ease-in and one ease-out.

.box { animation: move $t ease-in infinite alternate; } @keyframes move { 50% { transform: scale(.1); animation-timing-function: ease-out; } to { transform: translate(calc(50vw - 100%)); } }

See the Pen by thebabydino (@thebabydino) on CodePen.

Now all works fine, but what if we wanted different timing functions for the translation and scaling? The timing functions we've set mean the animation is slower at the beginning, faster in the middle and then slower again at the end. What if we wanted this to apply just to the translation, but not to the scale? What if we wanted the scaling to happen fast at the beginning, when it goes from 1 towards .1, slow in the middle when it's around .1 and then fast again at the end when it goes back to 1?

The animation timeline (live).

Well, it's just not possible to set different timing functions for different transform functions in the same chain. We cannot make the translation slow and the scaling fast at the beginning or the other way around in the middle. At least, not while what we animate is the transform property and they're part of the same transform chain.

The Old Workaround

There are of course ways of going around this issue. Traditionally, the solution has been to split the transform (and consequently, the animation) over multiple elements. This gives us the following structure:

<div class="wrap"> <div class="box"></div> </div>

We move the width property on the wrapper. Since div elements are block elements by default, this will also determine the width of its .box child without us having to set it explicitly. We keep the height on the .box however, as the height of a child (the .box in this case) also determines the height of its parent (the wrapper in this case).

We also move up the margin, transform and animation properties. In addition to this, we switch back to an ease-in-out timing function for this animation. We also modify the move set of @keyframes to what it was initially, so that we get rid of the scale().

.wrap { margin: .25*$d calc(50% - #{.5*$d}); width: $d; transform: translate(calc(-1*(50vw - 100%))); animation: move $t ease-in-out infinite alternate; } @keyframes move { to { transform: translate(calc(50vw - 100%)); } }

We create another set of @keyframes which we use for the actual .box element. This is an alternating animation of half the duration of the one producing the oscillatory motion.

.box { height: $d; background: #f90; animation: size .5*$t ease-out infinite alternate; } @keyframes size { to { transform: scale(.1); } }

We now have the result we wanted:

See the Pen by thebabydino (@thebabydino) on CodePen.

This is a solid workaround that doesn't add too much extra code, not to mention the fact that, in this particular case, we don't really need two elements, we could do with just one and one of its pseudo-elements. But if our transform chain gets longer, we have no choice but to add extra elements. And, in 2018, we can do better than that!

The Houdini Solution

Some of you may already know that CSS variables are not animatable (and I guess anyone who didn't just found out). If we try to use them in an animation, they just flip from one value to the other when half the time in between has elapsed.

Consider the initial example of the oscillating box (no scaling involved). Let's say we try to animate it using a custom property --x:

.box { /* same styles as before */ transform: translate(var(--x, calc(-1*(50vw - #{$d})))); animation: move $t ease-in-out infinite alternate } @keyframes move { to { --x: calc(50vw - #{$d}) } }

Sadly, this just results in a flip at 50%, the official reason being that browsers cannot know the type of the custom property (which doesn't make sense to me, but I guess that doesn't really matter).

See the Pen by thebabydino (@thebabydino) on CodePen.

But we can forget about all of this because now Houdini has entered the picture and we can register such custom properties so that we explicitly give them a type (the syntax).

For more info on this, check out the talk and slides by Serg Hospodarets.

CSS.registerProperty({ name: '--x', syntax: '<length>', initialValue: 0 });

We've set the initialValue to 0, because we have to set it to something and that something has to be a computationally independent value—that is, it cannot depend on anything we can set or change in the CSS and, given the initial and final translation values depend on the box dimensions, which we set in the CSS, calc(-1*(50vw - 100%)) is not valid here. It doesn't even work to set --x to calc(-1*(50vw - 100%)), we need to use calc(-1*(50vw - #{$d})) instead.

$d: 4em; $t: 1.5s; .box { margin: .25*$d auto; width: $d; height: $d; --x: calc(-1*(50vw - #{$d})); transform: translate(var(--x)); background: #f90; animation: move $t ease-in-out infinite alternate; } @keyframes move { to { --x: calc(50vw - #{$d}); } } The simple oscillating box we get using the new method (live demo, needs Houdini support).

For now, this only works in Blink browsers behind the Experimental Web Platform features flag. This can be enabled from chrome://flags (or, if you're using Opera, opera://flags):

The Experimental Web Platform features flag enabled in Chrome.

In all other browsers, we still see the flip at 50%.

Applying this to our oscillating and scaling demo means we introduce two custom properties we register and animate—one is the translation amount along the x axis (--x) and the other one is the uniform scaling factor (--f).

CSS.registerProperty({ /* same as before */ }); CSS.registerProperty({ name: '--f', syntax: '<number>', initialValue: 1 });

The relevant CSS is as follows:

.box { --x: calc(-1*(50vw - #{$d})); transform: translate(var(--x)) scale(var(--f)); animation: move $t ease-in-out infinite alternate, size .5*$t ease-out infinite alternate; } @keyframes move { to { --x: calc(50vw - #{$d}); } } @keyframes size { to { --f: .1 } } The oscillating and scaling with the new method (live demo, needs Houdini support). Better Looking Stuff

A simple oscillating and scaling square isn't the most exciting thing though, so let's see nicer demos!

More interesting examples. Left: rotating wavy grid of cubes. Right: bouncing square. The 3D version

Going from 2D to 3D, the square becomes a cube and, since just one cube isn't interesting enough, let's have a whole grid of them!

We consider the body to be our scene. In this scene, we have a 3D assembly of cubes (.a3d). These cubes are distributed on a grid of nr rows and nc columns:

- var nr = 13, nc = 13; - var n = nr*nc; .a3d while n-- .cube - var n6hedron= 6; // cube always has 6 faces while n6hedron-- .cube__face

The first thing we do is a few basic styles to create a scene with a perspective, put the whole assembly in the middle and put each cube face into its place. We won't be going into the details of how to build a CSS cube because I've already dedicated a very detailed article to this topic, so if you need a recap, check that one out!

The result so far can be seen below - all the cubes stacked up in the middle of the scene:

All the cubes stacked up in the middle (live demo).

For all these cubes, their front half is in front of the plane of the screen and their back half is behind the plane of the screen. In the plane of the screen, we have a square section of our cube. This square is identical to the ones representing the cube faces.

See the Pen by thebabydino (@thebabydino) on CodePen.

Next, we set the column (--i) and row (--j) indices on groups of cubes. Initially, we set both these indices to 0 for all cubes.

.cube { --i: 0; --j: 0; }

Since we have a number of cubes equal to the number of columns (nc) on every row, we then set the row index to 1 for all cubes after the first nc ones. Then, for all cubes after the first 2*nc ones, we set the row index to 2. And so on, until we've covered all nr rows:

style | .cube:nth-child(n + #{1*nc + 1}) { --j: 1 } | .cube:nth-child(n + #{2*nc + 1}) { --j: 2 } //- and so on | .cube:nth-child(n + #{(nr - 1)*nc + 1}) { --j: #{nr - 1} }

We can compact this in a loop:

style - for(var i = 1; i < nr; i++) { | .cube:nth-child(n + #{i*nc + 1}) { --j: #{i} } -}

Afterwards, we move on to setting the column indices. For the columns, we always need to skip a number of cubes equal to nc - 1 before we encounter another cube with the same index. So, for every cube, the nc-th cube after it is going to have the same index and we're going to have nc such groups of cubes.

(We only need to set the index to the last nc - 1, because all cubes have the column index set to 0 initially, so we can skip the first group containing the cubes for which the column index is 0 - no need to set --i again to the same value it already has.)

style | .cube:nth-child(#{nc}n + 2) { --i: 1 } | .cube:nth-child(#{nc}n + 3) { --i: 2 } //- and so on | .cube:nth-child(#{nc}n + #{nc}) { --i: #{nc - 1} }

This, too, can be compacted in a loop:

style - for(var i = 1; i < nc; i++) { | .cube:nth-child(#{nc}n + #{i + 1}) { --i: #{i} } -}

Now that we have all the row and column indices set, we can distribute these cubes on a 2D grid in the plane of the screen using a 2D translate() transform, according to the illustration below, where each cube is represented by its square section in the plane of the screen and the distances are measured in between transform-origin points (which are, by default, at 50% 50% 0, so dead in the middle of the square cube sections from the plane of the screen):

0) and on the first row (of index 0). All items on the second column (of index 1) are offset horizontally by and edge length. All items on the third column (of index 2) are offset horizontally by two edge lengths. In general, all items on the column of index i are offset horizontally by i edge lengths. All items on the last column (of index nc - 1) are offset horizontally by nc - 1 edge lengths. All items on the second row (of index 1) are offset vertically by and edge length. All items on the third row (of index 2) are offset vertically by two edge lengths. In general, all items on the row of index j are offset vertically by j edge lengths. All items on the last row (of index nr - 1) are offset vertically by nr - 1 edge lengths."/>How to create a basic grid starting from the position of the top left item (live). /* $l is the cube edge length */ .cube { /* same as before */ --x: calc(var(--i)*#{$l}); --y: calc(var(--j)*#{$l}); transform: translate(var(--x), var(--y)); }

This gives us a grid, but it's not in the middle of the screen.

The grid, having the midpoint of the top left cube in the middle of the screen (live demo).

Right now, it's the central point of the top left cube that's in the middle of the screen, as highlighted in the demo above. What we want is for the grid to be in the middle, meaning that we need to shift all cubes left and up (in the negative direction of both the x and y axes) by the horizontal and vertical differences between half the grid dimensions (calc(.5*var(--nc)*#{$l}) and calc(.5*var(--nr)*#{$l}), respectively) and the distances between the top left corner of the grid and the midpoint of the top left cube's vertical cross-section in the plane of the screen (these distances are each half the cube edge, or .5*$l).

The difference between the position of the grid midpoint and the top left item midpoint (live).

Subtracting these differences from the previous amounts, our code becomes:

.cube { /* same as before */ --x: calc(var(--i)*#{$l} - (.5*var(--nc)*#{$l} - .5*#{$l})); --y: calc(var(--j)*#{$l} - (.5*var(--nr)*#{$l} - .5*#{$l})); }

Or even better:

.cube { /* same as before */ --x: calc((var(--i) - .5*(var(--nc) - 1))*#{$l})); --y: calc((var(--j) - .5*(var(--nr) - 1))*#{$l})); }

We also need to make sure we set the --nc and --nr custom properties:

- var nr = 13, nc = 13; - var n = nr*nc; //- same as before .a3d(style=`--nc: ${nc}; --nr: ${nr}`) //- same as before

This gives us a grid that's in the middle of the viewport:

The grid is now in the middle (live).

We've also made the cube edge length $l smaller so that the grid fits within the viewport.

Alternatively, we can go for a CSS variable --l instead so that we can control the edge length depending on the number of columns and rows. The first step here is setting the maximum of the two to a --nmax variable:

- var nr = 13, nc = 13; - var n = nr*nc; //- same as before .a3d(style=`--nc: ${nc}; --nr: ${nr}; --max: ${Math.max(nc, nr)}`) //- same as before

Then, we set the edge length (--l) to something like 80% (completely arbitrary value) of the minimum viewport dimension over this maximum (--max):

.cube { /* same as before */ --l: calc(80vmin/var(--max)); }

Finally, we update the cube and face transforms, the face dimensions and margin to use --l instead of $l:

.cube { /* same as before */ --l: calc(80vmin/var(--max)); --x: calc((var(--i) - .5*(var(--nc) - 1))*var(--l)); --y: calc((var(--j) - .5*(var(--nr) - 1))*var(--l)); &__face { /* same as before */ margin: calc(-.5*var(--l)); width: var(--l); height: var(--l); transform: rotate3d(var(--i), var(--j), 0, calc(var(--m, 1)*#{$ba4gon})) translatez(calc(.5*var(--l))); } }

Now we have a nice responsive grid!

The grid is now in the middle and responsive such that it always fits within the viewport (live).

But it's an ugly one, so let's turn it into a pretty rainbow by making the color of each cube depend on its column index (--i):

.cube { /* same as before */ color: hsl(calc(var(--i)*360/var(--nc)), 65%, 65%); } The rainbow grid (live demo).

We've also made the scene background dark so that we have better contrast with the now lighter cube edges.

To spice things up even further, we add a row rotation around the y axis depending on the row index (--j):

.cube { /* same as before */ transform: rotateY(calc(var(--j)*90deg/var(--nr))) translate(var(--x), var(--y)); } The twisted grid (live demo).

We've also decreased the cube edge length --l and increased the perspective value in order to allow this twisted grid to fit in.

Now comes the fun part! For every cube, we animate its position back and forth along the z axis by half the grid width (we make the translate() a translate3d() and use an additional custom property --z that goes between calc(.5*var(--nc)*var(--l)) and calc(-.5*var(--nc)*var(--l))) and its size (via a uniform scale3d() of factor --f that goes between 1 and .1). This is pretty much the same thing we did for the square in our original example, except the motion now happens along the z axis, not along the x axis and the scaling happens in 3D, not just in 2D.

$t: 1s; .cube { /* same as before */ --z: calc(var(--m)*.5*var(--nc)*var(--l)); transform: rotateY(calc(var(--j)*90deg/var(--nr))) translate3d(var(--x), var(--y), var(--z)) scale3d(var(--f), var(--f), var(--f)); animation: a $t ease-in-out infinite alternate; animation-name: move, zoom; animation-duration: $t, .5*$t; } @keyframes move { to { --m: -1 } } @keyframes zoom { to { --f: .1 } }

This doesn't do anything until we register the multiplier --m and the scaling factor --f to give them a type and an initial value:

CSS.registerProperty({ name: '--m', syntax: '<number>', initialValue: 1 }); CSS.registerProperty({ name: '--f', syntax: '<number>', initialValue: 1 }); The animated grid (live demo, needs Houdini support).

At this point, all cubes animate at the same time. To make things more interesting, we add a delay that depends on both the column and row index:

animation-delay: calc((var(--i) + var(--j))*#{-2*$t}/(var(--nc) + var(--nr))); The waving grid effect (live).

The final touch is to add a rotation on the 3D assembly:

.a3d { top: 50%; left: 50%; animation: ry 8s linear infinite; } @keyframes ry { to { transform: rotateY(1turn); } }

We also make the faces opaque by giving them a black background and we have the final result:

The final result (live demo, needs Houdini support).

The performance for this is pretty bad, as it can be seen from the GIF recording above, but it's still interesting to see how far we can push things.

Hopping Square

I came across the original in a comment to another article and, as soon as I saw the code, I thought it was the perfect candidate for a makeover using some Houdini magic!

Let's start by understanding what is happening in the original code.

In the HTML, we have nine divs.

<div class="frame"> <div class="center"> <div class="down"> <div class="up"> <div class="squeeze"> <div class="rotate-in"> <div class="rotate-out"> <div class="square"></div> </div> </div> </div> </div> </div> <div class="shadow"></div> </div> </div>

Now, this animation is a lot more complex than anything I could ever come up with, but, even so, nine elements seems to be overkill. So let's take a look at the CSS, see what they're each used for and see how much we can simplify the code in preparation for switching to the Houdini-powered solution.

Let's start with the animated elements. The .down and .up elements each have an animation related to moving the square vertically:

/* original */ .down { position: relative; animation: down $duration ease-in infinite both; .up { animation: up $duration ease-in-out infinite both; /* the rest */ } } @keyframes down { 0% { transform: translateY(-100px); } 20%, 100% { transform: translateY(0); } } @keyframes up { 0%, 75% { transform: translateY(0); } 100% { transform: translateY(-100px); } }

With @keyframes and animations on both elements having the same duration, we can pull off a make-one-out-of-two trick.

In the case of the first set of @keyframes, all the action (going from -100px to 0) happens in the [0%, 20%] interval, while, in the case of the second one, all the action (going from 0 to -100px) happens in the [75%, 100%] interval. These two intervals don't intersect. Because of this and because both animations have the same duration we can add up the translation values at each keyframe.

  • at 0%, we have -100px from the first set of @keyframes and 0 from the second, which gives us -100px
  • at 20%, we have 0 from the first set of @keyframes and 0 from the second (as we have 0 for any frame from 0% to 75%), which gives us 0
  • at 75%, we have 0 from the first set of @keyframes (as we have 0 for any frame from 20% to 100%) and 0 from the second, which gives us 0
  • at 100%, we have 0 from the first set of @keyframes and -100px from the second, which gives us -100px

Our new code is as follows. We have removed the animation-fill-mode from the shorthand as it doesn't do anything in this case since our animation loops infinitely, has a non-zero duration and no delay:

/* new */ .jump { position: relative; transform: translateY(-100px); animation: jump $duration ease-in infinite; /* the rest */ } @keyframes jump { 20%, 75% { transform: translateY(0); animation-timing-function: ease-in-out; } }

Note that we have different timing functions for the two animations, so we need to switch between them in the @keyframes. We still have the same effect, but we got rid of one element and one set of @keyframes.

Next, we do the same thing for the .rotate-in and .rotate-out elements and their @keyframes:

/* original */ .rotate-in { animation: rotate-in $duration ease-out infinite both; .rotate-out { animation: rotate-out $duration ease-in infinite both; } } @keyframes rotate-in { 0% { transform: rotate(-135deg); } 20%, 100% { transform: rotate(0deg); } } @keyframes rotate-out { 0%, 80% { transform: rotate(0); } 100% { transform: rotate(135deg); } }

In a similar manner to the previous case, we add up the rotation values for each keyframe.

  • at 0%, we have -135deg from the first set of @keyframes and 0deg from the second, which gives us -135deg
  • at 20%, we have 0deg from the first set of @keyframes and 0deg from the second (as we have 0deg for any frame from 0% to 80%), which gives us 0deg
  • at 80%, we have 0deg from the first set of @keyframes (as we have 0deg for any frame from 20% to 100%) and 0deg from the second, which gives us 0deg
  • at 100%, we have 0deg from the first set of @keyframes and 135deg from the second, which gives us 135deg

This means we can compact things to:

/* new */ .rotate { transform: rotate(-135deg); animation: rotate $duration ease-out infinite; } @keyframes rotate { 20%, 80% { transform: rotate(0deg); animation-timing-function: ease-in; } 100% { transform: rotate(135deg); } }

We only have one element with a scaling transform that distorts our white square:

/* original */ .squeeze { transform-origin: 50% 100%; animation: squeeze $duration $easing infinite both; } @keyframes squeeze { 0%, 4% { transform: scale(1); } 45% { transform: scale(1.8, 0.4); } 100% { transform: scale(1); } }

There's not really much we can do here in terms of compacting the code, save for removing the animation-fill-mode and grouping the 100% keyframe with the 0% and 4% ones:

/* new */ .squeeze { transform-origin: 50% 100%; animation: squeeze $duration $easing infinite; } @keyframes squeeze { 0%, 4%, 100% { transform: scale(1); } 45% { transform: scale(1.8, .4); } }

The innermost element (.square) is only used to display the white box and has no transform set on it.

/* original */ .square { width: 100px; height: 100px; background: #fff; }

This means we can get rid of it if we move its styles to its parent element.

/* new */ $d: 6.25em; .rotate { width: $d; height: $d; transform: rotate(-135deg); background: #fff; animation: rotate $duration ease-out infinite; }

We got rid of three elements so far and our structure has become:

.frame .center .jump .squeeze .rotate .shadow

The outermost element (.frame) serves as a scene or container. This is the big blue square.

/* original */ .frame { position: absolute; top: 50%; left: 50%; width: 400px; height: 400px; margin-top: -200px; margin-left: -200px; border-radius: 2px; box-shadow: 1px 2px 10px 0px rgba(0,0,0,0.2); overflow: hidden; background: #3498db; color: #fff; font-family: 'Open Sans', Helvetica, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }

There's no text in this demo, so we can get rid of the text-related properties. We can also get rid of the color property since, not only do we not have text anywhere in this demo, but we're also not using this for any borders, shadows, backgrounds (via currentColor) and so on.

We can also avoid taking this containing element out of the document flow by using a flexbox layout on the body. This also eliminates the offsets and the margin properties.

/* new */ $s: 4*$d; body { display: flex; align-items: center; justify-content: center; height: 100vh; } .frame { overflow: hidden; position: relative; width: $s; height: $s; border-radius: 2px; box-shadow: 1px 2px 10px rgba(#000, .2); background: #3498db; }

We've also tied the dimensions of this element to those of the hopping square.

The .center element is only used for positioning its direct children (.jump and .shadow), so we can take it out altogether and use the offsets on it directly on these children.

We use absolute positioning on all .frame descendants. This makes the .jump and .squeeze elements 0x0 boxes, so we tweak the transform-origin for the squeezing transform (100% of 0 is always 0, but the value we want is half the square edge length .5*$d). We also set a margin of minus half the square edge length (-.5*$d) on the .rotate element (to compensate for the translate(-50%, -50%) we had on the removed .center element).

/* new */ .frame * { position: absolute, } .jump { top: $top; left: $left; /* same as before */ } .squeeze { transform-origin: 50% .5*$d; /* same as before */ } .rotate { margin: -.5*$d; /* same as before */ }

Finally, let's take a look at the .shadow element.

/* original */ .shadow { position: absolute; z-index: -1; bottom: -2px; left: -4px; right: -4px; height: 2px; border-radius: 50%; background: rgba(0,0,0,0.2); box-shadow: 0 0 0px 8px rgba(0,0,0,0.2); animation: shadow $duration ease-in-out infinite both; } @keyframes shadow { 0%, 100% { transform: scaleX(.5); } 45%, 50% { transform: scaleX(1.8); } }

We're of course removing the position since we've already set that for all descendants of the .frame. We can also get rid of the z-index if we move the .shadow before the .jump element in the DOM.

Next, we have the offsets. The midpoint of the shadow is offset by $left (just like the .jump element) horizontally and by $top plus half a square edge length (.5*$d) vertically.

We see a height that's set to 2px. Along the other axis, the width computes to the square's edge length ($d) plus 4px from the left and 4px from the right. That's plus 8px in total. But one thing we notice is that the box-shadow with an 8px spread and no blur is just an extension of the background. So we can just increase the dimensions of the our element by twice the spread along both axes and get rid of the box-shadow altogether.

Just like in the case of the other elements, we also get rid of the animation-fill-mode from the animation shorthand:

/* new */ .shadow { margin: .5*($d - $sh-h) (-.5*$sh-w); width: $sh-w; height: $sh-h; border-radius: 50%; transform: scaleX(.5); background: rgba(#000, .2); animation: shadow $duration ease-in-out infinite; } @keyframes shadow { 45%, 50% { transform: scaleX(1.8); } }

We've now reduced the code in the original demo by about 40% while still getting the same result.

See the Pen by thebabydino (@thebabydino) on CodePen.

Our next step is to merge the .jump, .squeeze and rotate components into one, so that we go from three elements to a single one. Just as a reminder, the relevant styles we have at this point are:

.jump { transform: translateY(-100px); animation: jump $duration ease-in infinite; } .squeeze { transform-origin: 50% .5*$d; animation: squeeze $duration $easing infinite; } .rotate { transform: rotate(-135deg); animation: rotate $duration ease-out infinite; } @keyframes jump { 20%, 75% { transform: translateY(0); animation-timing-function: ease-in-out; } } @keyframes squeeze { 0%, 4%, 100% { transform: scale(1); } 45% { transform: scale(1.8, .4); } } @keyframes rotate { 20%, 80% { transform: rotate(0deg); animation-timing-function: ease-in; } 100% { transform: rotate(135deg); } }

The only problem here is that the scaling transform has a transform-origin that's different from the default 50% 50%. Fortunately, we can go around that.

Any transform with a transform-origin different from the default is equivalent to a transform chain with default transform-origin that first translates the element such that its default transform-origin point (the 50% 50% point in the case of HTML elements and the 0 0 point of the viewBox in the case of SVG elements) goes to the desired transform-origin, applies the actual transformation we want (scaling, rotation, shearing, a combination of these... doesn't matter) and then applies the reverse translation (the values for each of the axes of coordinates are multiplied by -1).

Any transform with a transform with a transform-origin different from the default is equivalent to a chain that translates the point of the default transform-origin to that of the custom one, performs the desired transform and then reverses the initial translation (live demo).

Putting this into code means that if we have any transform with transform-origin: $x1 $y1, the following two are equivalent:

/* transform on HTML element with transform-origin != default */ transform-origin: $x1 $y1; transform: var(--transform); /* can be rotation, scaling, shearing */ /* equivalent transform chain on HTML element with default transform-origin */ transform: translate(calc(#{$x1} - 50%), calc(#{$y1} - 50%)) var(--transform) translate(calc(50% - #{$x1}), calc(50% - $y1);

In our particular case, we have the default transform-origin value along the x axis, so we only need to perform a translation along the y axis. By also replacing the hardcoded values with variables, we get the following transform chain:

transform: translateY(var(--y)) translateY(.5*$d) scale(var(--fx), var(--fy)) translateY(-.5*$d) rotate(var(--az));

We can compact this a bit by joining the first two translations:

transform: translateY(calc(var(--y) + #{.5*$d})) scale(var(--fx), var(--fy)) translateY(-.5*$d) rotate(var(--az));

We also put the three animations on the three elements into just one:

animation: jump $duration ease-in infinite, squeeze $duration $easing infinite, rotate $duration ease-out infinite;

And we modify the @keyframes so that we now animate the newly-introduced custom properties --y, --fx, --fy and --az:

@keyframes jump { 20%, 75% { --y: 0; animation-timing-function: ease-in-out; } } @keyframes squeeze { 0%, 4%, 100% { --fx: 1; --fy: 1 } 45% { --fx: 1.8; --fy: .4 } } @keyframes rotate { 20%, 80% { --az: 0deg; animation-timing-function: ease-in; } 100% { --az: 135deg } }

However, this won't work unless we register these CSS variables we have introduced and want to animate:

CSS.registerProperty({ 'name': '--y', 'syntax': '<length>', 'initialValue': '-100px' }); CSS.registerProperty({ 'name': '--fx', 'syntax': '<number>', 'initialValue': 1 }); /* exactly the same for --fy */ CSS.registerProperty({ 'name': '--az', 'syntax': '<angle>', 'initialValue': '-135deg' });

We now have a working demo of the method animating CSS variables. But given that our structure is now one wrapper with two children, we can reduce it further to one element and two pseudo-elements, thus getting the final version which can be seen below. It's worth noting that this only works in Blink browsers with the Experimental Web Platform features flag enabled.

The final result (live, needs Houdini support)

The post What Houdini Means for Animating Transforms appeared first on CSS-Tricks.

Productivity Tip: Time Tracking and Task Lists, Unite!

Css Tricks - Tue, 03/06/2018 - 11:56am

I've shared this little productivity tip with enough folks who have found it useful and figured I'd make a post out of it.

I love time tracking and I love task lists, but boy do I hate managing them both. So, I've been using my time tracker as my task list.

I use Harvest for time tracking. It allows you to create time entries in the future and I suspect many other time tracking apps do the same. That means today I can enter all the time entries I plan on doing tomorrow. Or, if I'm feeling super organized, I can create entries for the following week. All of my tasks are right there in front of me and ready to clock my time.

If I don't get to a task that day? No worries. Harvest has a subtle feature that allows me to move a time entry from one day to another. Now, I'm good to go for the next day.

Again, other apps are probably capable of doing the same.

I know, it's a super small thing but it delights me every day and helps me manage two important things in one.

The post Productivity Tip: Time Tracking and Task Lists, Unite! appeared first on CSS-Tricks.

CSS Techniques and Effects for Knockout Text

Css Tricks - Tue, 03/06/2018 - 5:20am

Knockout text is a technique where words are clipped out of an element and reveal the background. In other words, you only see the background because the letters are knocking out holes. It’s appealing because it opens up typographic styles that we don’t get out of traditional CSS properties, like color.

While we’ve seen a number of ways to accomplish knockout text in the past, there are some modern CSS properties we can use now and even enhance the effect further, like transitions and animations. Let’s see them in action.

Mix Blend Modes

There are four blend modes that effortlessly make text cutouts: multiply, screen, darken, and lighten. Applying these to the top element of a stack of image and text, text being at top, creates the knockout design.

Even though, in most cases, either black or white is used in these blend modes to get a clear distinction between the text and the background, I prefer using a darker or lighter color instead to keep the back image slightly visible, like this:

<div class="backdrop"> <p class="text">Tait?</p> </div> /* Background layer with an image */ .backdrop { background: url("/path/to/image.jpg") center; ... } /* Dark foreground layer, with white text, set to "multiply" mix blend mode */ .text { color: white; background: rgb(59, 2, 6); mix-blend-mode: multiply; ... }

See the Pen CSS Knockout Text by Preethi (@rpsthecoder) on CodePen.

Using a darker (or lighter) color also creates a nice "theme" going on with the image shown through the text.

The multiply blend mode keeps darker colors dark and the lighter colors let through whatever’s behind them: a black portion on the top layer will be fully opaque and white will be fully transparent.

The effects of the multiply mix blend mode.

In the above example, the white text became completely see-through while the darker color around it lets the image behind be seen only a little, as the darker shades remain unaffected.

The screen blend mode reverses the roles: darker colors create translucence while lighter shades remain light and block what’s behind.

The darken and lighten blend modes are similar to multiply and screen, respectively, except that details are lost on the portions of the back image that can be seen. Rather than mixing the shades, the modes choose the darker or the lighter shade of the two layers that are shown.

See below the difference in how the four modes have blended the colors:

/* Knockout text within a dark area */ .multiply { color: white; mix-blend-mode: multiply; background-color: rgb(59, 2, 6); } /* Knockout text within a bright area */ .screen { color: black; mix-blend-mode: screen; background-color: rgb(244, 220, 211); } /* Knockout text within a dark area that's less detailed */ .darken { color: white; mix-blend-mode: darken; background-color: rgb(59, 2, 6); } /* Knockout text within a light area that's less detailed */ .lighten { color: black; mix-blend-mode: lighten; background-color: rgb(244, 220, 211); }

See the Pen CSS Knockout Text by Preethi (@rpsthecoder) on CodePen.

Using a blend mode is the most convenient option to get a knockout text effect, because it allows us to apply additional styles that other techniques might not allow.

Let’s take a closer look at the styles we can use to enhance the knockout effect.

Shadow Blur

Adding a white/black or a light/dark text shadow to the text creates a blurred effect. For example, let’s say I add a text-shadow that's with a large blur radius value:

.text { text-shadow: 0 0 9px white; ... }

Now the edges are less crisp and give a sort of cloudy effect:

See the Pen CSS Blurred Knockout Text by Preethi (@rpsthecoder) on CodePen.

Animation

We can even make things move around a little. For example, let’s take build on the text-shadow idea we looked at above and put some movement on it to make it appear that the text is glowing:

.text { animation: glow 3s infinite; ... } @keyframes glow { 0% { text-shadow: 0 0 10px white; } 15% { text-shadow: 2px 2px 10px rgba(255, 255, 255, 1), -2px -2px 10px rgba(255, 255, 255, 1); } 30% { text-shadow: 2px 2px 4px rgba(255, 255, 255, .7), -2px -2px 4px rgba(255, 255, 255, .7); } 50% { text-shadow: 20px 20px 50px rgba(255, 255, 255, .5), -20px -20px 50px rgba(255, 255, 255, .5); } }

See the Pen CSS knockout Text Glow Animation by Preethi (@rpsthecoder) on CodePen.

Transition

Transition is another property we can apply to our knockout text and that opens up even more interesting possibilities, like using text-indent on pseudo-classes like :hover.

Here’s how we can use transition on pseudo-classes to introduce a new element into the knockout text:

/* The knockout text */ .text { transition: text-indent .5s; ... } /* On hover, trigger the transition */ .text:hover { text-indent: 5px; transition: text-indent .5s; } /* The thing that slides in on hover */ .text:hover::before { display: inline-block; content: '&#x2708;?'; }

See the Pen CSS Knockout Text Transition by Preethi (@rpsthecoder) on CodePen.

Background Clip

The background-clip CSS property set with the text value clips a background to the shape of its foreground text.

Using background-clip: text <p class="text">Tait?</p> .text { background: url("/path/to/image.jpg") center; background-clip: text; color: transparent; ... }

See the Pen Knockout Text by Preethi (@rpsthecoder) on CodePen.

The transparent text shows the image behind it that’s already cut to the shape of the text. Although this is a true knockout text approach—it literally removes the text’s surrounding background on screen—having no background left to bleed into or move over leaves little space for other effects like blur, or moving text, to take place. That’s where mix-blend-mode has an advantage.

CSS Mask

The first technique we looked at employs masking, a concept where shapes are created on a foreground layer and use color to determine how much of the shape shows the background. The black parts of the foreground hide (or "mask") and white parts reveal the background, or vice-versa. Any gray value between black and white is treated as varying degrees of partial transparency.

CSS mask works the same way: you straight up declare an image to be the mask applied over another image and, depending on the type of the mask, we get a portion cut out. As of writing this post CSS mask is fully supported in Firefox only.

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

DesktopChromeOperaFirefoxIEEdgeSafari67*52*53NoNoTP*Mobile / TabletiOS SafariOpera MobileOpera MiniAndroidAndroid ChromeAndroid Firefox11.3*37*No62*64*57

Since we are looking specifically into knockout text, the mask needs to be made from text. This is a great use for SVG <mask>, which can create masks from SVG shapes and texts.

<div class="backdrop"> <p class="text"></p> </div> <svg> <defs> <mask id="m"> <rect width="100%" height="100%" fill="white" /> <text x="50%" y="75%" text-anchor="middle">Tait?</text> </mask> </defs> </svg> /* Background layer with an image */ .backdrop { background: url("/path/to/image.jpg") center; ... } /* Dark foreground layer with masking applied */ .text { background-color: rgba(59, 2, 6, 1); mask-type: luminance; /* Referrer to an SVG mask */ mask: url("#m"); ... } svg { width: 75vw; height: 20vw; } /* SVG text inside the mask */ text { font: bolder 12vw 'Alfa Slab One'; }

See the Pen Knockout Text by Preethi (@rpsthecoder) on CodePen.

The luminance value on the mask-type property of the foreground element implements a masking mechanism where parts of this layer, corresponding to the black parts of the mask, become transparent. The parts corresponding to the white portions of the mask remain opaque. The mask property uses the url() value to designate the SVG element used for the mask.

SVG’s <mask> element creates a mask imaged from its contents. The contents I made inside the <mask> are a white rectangle (<rect>) and black text (<text>). The text, being black, brings up the portion of the image behind it to the view after masking.

Blurring, Animation, and Transition

CSS masks open up the same blur and animation effects we were able to use with mix-blend-mode.

That same glowing text from we used before applies here as well, this time applied directly to the <text> element of the SVG:

text { font: bolder 12vw 'Alfa Slab One'; animation: glow 3s infinite; } @keyframes glow { 0% { text-shadow: 0 0 10px white; } 15% { text-shadow: 2px 2px 10px rgba(255, 255, 255, 1), -2px -2px 10px rgba(255, 255, 255, 1); } 30% { text-shadow: 2px 2px 4px rgba(255, 255, 255, .7), -2px -2px 4px rgba(255, 255, 255, .7); } 50% { text-shadow: 20px 20px 50px rgba(255, 255, 255, .5), -20px -20px 50px rgba(255, 255, 255, .5); } }

However, unlike mix-blend-mode, not all the same properties can be animated. For example, text-indent won’t work here and neither will transform. It’s true that CSS transforms can be applied to SVG elements but because our mask is actually being used as a mask in its truest form, browsers might not apply those transformations.

We can always inject a transform SVG attribute using JavaScript, delivering those transformations to the elements inside the mask:

t = document.querySelector('text'); b = document.querySelector('.backdrop'); b.onmouseover = ()=>{ t.setAttribute('transform', 'translate(20)'); } b.onmouseout = ()=>{ t.removeAttribute('transform'); } Conclusion

When it comes to browser support and production safe code, CSS mask lags behind due to being limited to Firefox support. The blend modes mentioned in this post are supported in almost all the major browsers, except Edge. The background-clip property is also supported by all the browsers, but still requires -webkit prefix.

In terms of result, both blend modes and mask give similar output. Between background-clip and mix-blend-mode values, it’ll be the choice of the design that’ll lead to choosing one over another. What you can achieve with background-clip can also be done by blending, provided you’re using either only black or white background matching the page’s body.

TLDR; knockout text method uno: apply one of the four knockout-friendly blend modes to the top layer of a text-image stack and use dark/light (or black/white) color combination on the text and its enclosure. Method dos: set background-clip to text in the element carrying both a background image and a transparent text. Method tres: use CSS masking on a solid foreground with an image behind and dictate the cutout using an SVG text mask.

The post CSS Techniques and Effects for Knockout Text appeared first on CSS-Tricks.

React Native: A Better DOM?

Css Tricks - Mon, 03/05/2018 - 9:38am

How do we convince web developers that React Native has already solved many of the hardest GUI problems for them? Go back in time and release React Native before React DOM? Is there an easier way…

— Nicolas (@necolas) March 1, 2018

Like a lot of people in this Twitter thread, I didn't really understand that React Native was even for building on the web. I thought it was a way to write React to build native mobile apps. Nicolas has a whole "React Native for Web" repo though, explaining otherwise. Plus a conference talk.

It probably doesn't help that the tagline is "Build native mobile apps using JavaScript and React." I suppose, it does do that (e.g. build an iOS or Android app), but it also can build your web app, which could mean... single code base?

Several of the replies suggest "a better DOM" which is interesting. Or, as Nicolas points out, it's kinda like "web: the good parts" as much of it is an intentionally limited subset of the involved platforms in order to simplify things and make them interoperable.

Obviously, this isn't for every project. But, if you have a React-based website already, and either have or want a native mobile app, then it seems like this is worth exploring.

Direct Link to ArticlePermalink

The post React Native: A Better DOM? appeared first on CSS-Tricks.

Three Techniques for Performant Custom Font Usage

Css Tricks - Mon, 03/05/2018 - 4:16am

There’s a lot of good news in the world of web fonts!

  1. The forthcoming version of Microsoft Edge will finally implement unicode-range, the last modern browser to do so.
  2. Preload and font-display are landing in Safari and Firefox.
  3. Variable fonts are shipping everywhere.

Using custom fonts in a performant way is becoming far easier. Let’s take a peek at some things we can do when using custom fonts to make sure we’re being as performant as we can be.

1) Reduce the File Size

Far from containing only numbers, the Latin alphabet and common punctuation, many fonts support multiple languages and include thousands of additional glyphs, adding significantly to the file size.

The Mac tool Font Book can display all the available glyphs in a font.

In these cases, subsetting is useful. Subsetting is the removal of characters you don’t need. You can do this using the command line tool pyftsubset, which can be downloaded as part of FontTools. (The website FontSquirrel offers a GUI for subsetting. Its a popular alternative thats also worth considering - but be aware that it isn't compatible with variable fonts). The easiest way to specify the characters you want to keep is with unicode. Unicode is a standard that provides a unique code to every character from the world's languages.

Unicode has assigned a number to many thousands of characters. We are interested in a very limited subset.

The pyftsubset command is followed by the name of your font file and the unicode specifying your chosen unicode range. The following is a subset that caters to the English language.

pyftsubset SourceSansPro.ttf --unicodes="U+0020-007F"

You can then reduce the size of the font further by compressing to a woff2 file format. You could do this with a separate command line utility from Google, or just add some extra options when you run the pyftsubset command:

pyftsubset SourceSansPro.ttf --unicodes="U+0020-007F" --flavor="woff2" --output-file="SourceSansPro.woff2"

If you’re confident you’re not going to use any of the characters you’ve removed from the font, this is all you need to do — you’ve radically lowered the size of the font, and you can now use it in @font-face as usual. But what if some of the pages on your site make use of additional glyphs that have been removed from the font? You’ll want to avoid any characters being displayed with a fallback typeface. To solve this potential issue, be sure to note the range you use when creating your subset with pyftsubset. You’ll want to specify it within the @font-face block.

@font-face { font-family: 'Source Sans Pro'; src: url('/fonts/SourceSansPro.woff2') format('woff2'); unicode-range: U+0020-007F; /* The bare minimum for the English Language */ }

The above unicode-range is specifying characters that are likely to be used across all pages of the site. This font will therefore be downloaded on every page.

Firefox has the best font-related dev-tooling. Look in the Fonts tab of Firefox to find out which fonts are being used. In other browsers look in the Network tab to find out which font files are being downloaded.

I’ll also create another file with pyftsubset. This won’t repeat any of the characters from the the first file. Rather, it will only contain glyphs that are rarely used on the site.

pyftsubset SourceSansPro.ttf --unicodes="U+00A0-00FF,U+0100-017F" --output-file="SourceSansProExtra.ttf"

By again specifying a unicode-range within another @font-face declaration, we ensure the file will be downloaded only when its needed — when a character on the page matches against the specified range.

@font-face { font-family: 'Source Sans Pro'; src: url('/fonts/SourceSansProExtra.woff2') format('woff2'); unicode-range: U+00A0-00FF, U+0100-017F; /* additional glyphs */ } Adding some random French accents to the HTML will cause the extra subset file to be downloaded.

You should cater the range to your own purposes. If you have any unusual punctuation or letters in your header or footer, make sure you include them in your main font file, otherwise both files will always be downloaded. For the site I work on, for example, I’ve included the © symbol (U+00A) into the main subset, because it's included at the bottom of every page. You can find a handy list of unicode characters on Wikipedia.

2) Load Key Font Files as Early as Possible

Wow.https://t.co/6CfAK6mjYs CSS has 100 different @?font-face blocks.

That’s, well… that’s a lot. pic.twitter.com/jj2NFS4vd5

— Zach “Fancy Letters” Leatherman (@zachleat) June 25, 2016

Imagine the only CSS in your stylesheet is one hundred @font-face declarations and the following lines of code:

* { font-family: 'Lora'; font-style: italic; font-weight: bold; }

How many fonts would the browser download? One hundred? The answer is,
just one. Fonts are lazy loaded by default. This is a smart move by browser vendors — only download what is actually needed. There is, however, a downside. To know what font variants are being used, the browser must parse all the HTML and all the CSS. Only after all of that will any font files be requested. If we want to kick things off more quickly, we have to use preloading.

Preloading is as simple as adding a link tag into the head of the document:

<link rel="preload" href="/fonts/Lora-Regular.woff2" as="font" type="font/woff2" crossorigin> The preloaded font is the first resource to download after the HTML document itself.

It's not a good idea to preload too many resources, or wastefully preload any resource that might not be used on the page. For this reason I stick to preloading only the normal weight, non-italic file. I’m less concerned about a late-loading bold or italic file as those styles are used less often across the site. A flash of unstyled text (FOUT) for these font files is less disruptive than a FOUT of the regular Roman font, which makes up the bulk of the text. If you’re using a variable font, this is a non-issue. However, preloading variable fonts currently presents its own problem.

Should You Preload a Variable Font?

It's clearly wasteful to send a browser a resource it can’t use. That’s why we include a type attribute that specifies the MIME type of the resource. If the resource isn’t supported by the browser, it won’t be downloaded. In the case of fonts, we specify type="font/woff2". Edge, Safari and Firefox are implementing both variable fonts and preload at the same time, so there isn’t an issue for those browsers. Chrome and Opera, however, have supported preload since 2016. Variable fonts are a brand new addition, whereas woff2 has been supported since 2014. For that reason, preloading a variable woff2 will force older versions of Chrome and Opera to download a resource they won’t know what to do with.

Ideally, we’d be able to specify the resource as a variable font within the type attribute, but this is largely unnecessary. Chrome is evergreen. There’s a new release every six weeks and it automatically updates in the background. Relatively few visitors will be using an ancient version. Once variable fonts have been around in Chrome for several versions, it will be safe to preload.

3) Managing FOUT and FOIT

So far we’ve looked at speeding up font loading. A good user experience is not just about the total loading time of a page or a font — it's about how users experience the site while it's loading. If you want users to stick around, you need to think about perceived performance. The different browsers aren’t consistent in how they manage font loading. We can now easily decide the loading strategy for ourselves using the CSS font-display property.

The font-display property is defined within an @font-face block.

@font-face { font-family: ExampleFont; src: url(/fonts/SourceSansPro.woff2) format('woff2'); font-display: fallback; }

The available options are block, optional, swap and fallback. Which should you pick?

The optional value sounds like a good bet — fonts are a nice-to-have enhancement, but not a strict necessity. In practice, I’ve found it a bad choice. Seeing the typeface of a page change upon navigation or reload is unexpected for users, and somewhat disconcerting. The window of time given for the custom font to load is incredibly small. If you’re preloading the font, it's likely to load within this narrow timeframe. If not, users will often experience the fallback web safe font — even on a good connection with an expensive computer. That’s not great for brand consistency. If you’ve put much time into finding a similar fallback font, this might be an acceptable option, but if it's that similar and performance is paramount, then maybe you should reconsider why you’re using a custom font at all.

The swap value is a better option. The web safe fallback font is shown immediately and replaced by the custom font once it's loaded. Unfortunately, the swap period is infinite, so on a slow connection, the font could change after the user has already become immersed in the text, creating a jarring and disconcerting experience.

The fallback value is similar to the default behavior of most browsers — a short block period of invisible text followed by a fallback font if the custom font still hasn’t loaded. Unlike swap, there is a timeout period. If the custom font doesn’t load within three seconds, the font doesn’t change — the user is left with the fallback. You can read a whole article on CSS-Tricks about font-display and decide which option works best for you.

Wrapping Up

Text makes up the majority of content on most websites. Font-loading is therefore an important piece in the performance puzzle. If you want to keep up with font loading developments, Zach Leatherman just started a newletter about the subject.

The post Three Techniques for Performant Custom Font Usage appeared first on CSS-Tricks.

V6: Color

Css Tricks - Fri, 03/02/2018 - 12:20pm

This is a lovely little post by Rob Weychert about color theory, hierarchy and how to implement those colors in a design system. It’s particularly interesting hearing what Rob has to say on HSL, which is an alternative way of setting the color of an element in CSS:

For color adjustment, HSL brings a level of granular control to the process that other color systems lack. And for implementation, Sass lets me assign color values to variables, which make system-wide iteration quick and painless.

Direct Link to ArticlePermalink

The post V6: Color appeared first on CSS-Tricks.

Building a Serverless CMS Powered by Vue.js

Css Tricks - Fri, 03/02/2018 - 4:17am

Vue.js, a progressive framework for building user interfaces, is gaining in popularity among developers. But why yet another JavaScript framework? Vue has learned from the experiences of Angular and React and many see it as simpler to implement and understand.

Vue.js is lightweight and easily adoptable. It’s reactive and component-based, allowing you to create pluggable components you can add to any project. Most importantly for this tutorial, Vue and its hallmark incremental adoptability allows you to try Vue without putting your existing code base at risk.

Vue works well with serverless application architectures. Serverless architectures are becoming the preferred architecture for many developers because it allows them to create and fine-tune products efficiently without having to bear the burdens (server maintenance, outages, and scaling bottlenecks) of traditional server-based architecture. Sarah Drasner recently wrote an entire series on how to create a serverless checkout flow powered by Vue and is a good example of this in practice.

In this tutorial, you’ll learn how to build a marketing website as a serverless Vue.js application using ButterCMS. ButterCMS is a headless CMS and blogging platform that lets you build CMS-powered apps using any programming language, including Vue. There are other options for going with a headless CMS, but I happen to develop for ButterCMS and know it extremely well so that's what we'll be using in our examples.

This tutorial will show you how to add performant content APIs to your Vue.js application. These APIs are easy to navigate even for the non-technical members of your team, enabling you to enjoy agile content management without having to spin up and maintain your own CMS infrastructure.

Specifically, we’ll examine code samples for three content types we might find on a marketing website: customer case studies, frequently asked questions, and blog posts.

Note that the designs in the screenshots we use throughout this post will likely differ from what you build and are styled with light CSS for demonstration. Your real design would use the global styling from your app making the pages look consistent with the rest of your site.

Getting Started

We're using ButterCMS as our content management system, so let's install it:

npm install buttercms --save

Once installed, you can proceed with the following examples.

Example 1: Customer Case Studies

Let's start by making it possible for any non-technical person on your team to add customer case studies to the site. In this case, we'll create a page that can hold all of the published case studies that promote the product or service we're selling which, when clicked, open up the page for that case study.

Step 1: Setup Customer Case Study Page Type

Using the dashboard on ButterCMS, you can create a "page type" entitled "Customer Case Study” and define the content fields. Once you’ve done this, you can create your first page. Specify the name and URL of the page using the ButterCMS dashboard and complete the populate the content fields we just defined.

Once this is all done, the ButterCMS API will return your defined page in JSON format. It should look something like this:

{ "data": { "slug": "acme-co", "fields": { "facebook_open_graph_title": "Acme Co loves ButterCMS", "seo_title": "Acme Co Customer Case Study", "headline": "Acme Co saved 200% on Anvil costs with ButterCMS", "testimonial": "<p>We've been able to make anvils faster than ever before! - <em>Chief Anvil Maker</em></p>\r\n<p><img src="https://cdn.buttercms.com/NiA3IIP3Ssurz5eNJ15a" alt="" caption="false" width="249" height="249" /></p>", "customer_logo": "https://cdn.buttercms.com/c8oSTGcwQDC5I58km5WV", } } } Step 2: Integrating Your App

Next, open your code editor and create a file called buttercms.js in your /src directory.

If you don’t have an existing project, create one by entering the following:

vue init webpack buttercms-project cd buttercms-project npm i npm i -S buttercms npm run dev

Then, in src/buttercms.js:

import Butter from 'buttercms'; const butter = Butter('your_api_token');

Now, update the routes in your app. This is done in router/index.js:

import Vue from 'vue' import Router from 'vue-router' import CustomersHome from '@/components/CustomersHome' import CustomerPage from '@/components/CustomerPage' Vue.use(Router) export default new Router({ mode: 'history', routes: [ { path: '/customers/', name: 'customers-home', component: CustomersHome }, { path: '/customers/:slug', name: 'customer-page', component: CustomerPage } ] })

It's worth checking out Hassan Djirdeh's recent post "Let's Build a Custom Vue Router" for a deep-dive on using Vue's routing library and methods for creating custom routes.

You have content in a data file, which is great, but now you need a page that uses the content. You're going to define a getpages() method that fetches all of the case study pages so you can render them together on a single landing page to create an index of them all. This will be a "homepage" for all of the published case studies.

In components/CustomersHome.vue you add:

<script> // import ButterCMS from import { butter } from '@/buttercms' export default { name: 'customers-home', data() { return { page_title: 'Customers', // Create array to hold the pages from ButterCMS API pages: [] } }, methods: { // Get List of Customer Pages getPages() { butter.page.list('customer_case_study') .then((res) => { // console.log(res.data.data) // Check the results in the console this.pages = res.data.data }) } }, created() { // Fire on page creation this.getPages() } } </script>

...and to display the results, one by one:

<template> <div id="customers-home"> <h1>{{ page_title }}</h1> <div v-for="(page,index) in pages" :key="page.slug + '_' + index"> <router-link :to="'/customers/' + page.slug"> <div> <img :src="page.fields.customer_logo" alt=""> <h2>{{ page.fields.headline }}</h2> </div> </router-link> </div> </div> </template>

Here's an example of something close to what you have so far after publishing one case study:

Now, you're going to set up the page we get when clicking on a case study from the homepage. To do so, in components/CustomerPage.vue we define a getPage() method to get a particular customer page based on its slug:

<script> import { butter } from '@/buttercms' export default { name: 'customer-page', data() { return { slug: this.$route.params.slug, page: { slug: '', fields: {} } } }, methods: { getPage() { butter.page.retrieve('customer_case_study', this.slug) .then((res) => { console.log(res.data.data) this.page = res.data.data }).catch((res) => { console.log(res) }) } }, created() { this.getPage() } } </script>

And, just like you did for the case studies homepage, you need to display the content by defining a template and calling the content fields you want to show:

<template> <div id="customer-page"> <figure> <img :src="page.fields.customer_logo"> </figure> <h1>{{ page.fields.headline }}</h1> <h3>Testimonials</h3> <div v-html="page.fields.testimonial"></div> <div v-html="page.fields.body"></div> </div> </template>

This should give you something like this:

Success! Now you can go directly to a page that lists all published case studies and click on any of them to be taken to the detail page for a specific case study post.

Example 2: Frequently Asked Questions

Now lets's walk through how to create a Frequently Asked Questions (FAQ) page for the app. We’ll be using ButterCMS "Content Fields” for this. Content fields are simply global pieces of content that can be managed by your team. This content can span multiple pages and each content field has a unique ID for querying, as you’ll see below.

Step 1: Setup Content Fields

First, you’ll need to set up some custom content fields. Using the dashboard, you can set up a workspace to organize content fields. Workspaces will allow content editors to curate content without affecting development or the API.

Once you're in a workspace, click the button to create a new content field. Choose the "Object" type and use "FAQ Headline" as the name of the field. It will have an API slug of faq_headline.

After saving, add another field but this time choose the "Collection" type and use "FAQ Items" as the name of the field. This one will have an faq_items API slug. On the next screen, set up two properties for items in the collection and go back to your workspace to update your heading and add some FAQ posts.

Step 2: Integrating Your App

Now that you’ve created dynamic content using content fields, it’s time to display it in the app. To do this, you’ll fetch the fields with an API call and reference them in your view. First, set up a route to your FAQ page:

Let's add FAQ routes in router/index.js:

import Vue from 'vue' import Router from 'vue-router' import FAQ from '@/components/FAQ' Vue.use(Router) export default new Router({ mode: 'history', routes: [ { path: '/faq', name: 'faq', component: FAQ } ] })

Then create components/FAQ.vue with a call to get the FAQ content from the API:

<script> import { butter } from '@/buttercms' export default { name: 'faq', data() { return { page_title: 'FAQ', faq_items: [] } }, methods: { getFaqs() { butter.content.retrieve(['faq_headline', 'faq_items']) .then((res) => { console.log(res.data.data) this.page_title = res.data.data.faq_headline this.faq_items = res.data.data.faq_items }) } }, created() { this.getFaqs() } } </script>

Notice that we predefined page_title as FAQ and then updated it with the API call to the FAQ content fields.

Define the <template>:

<template> <div id="faq"> <h1>{{ page_title }}</h1> <div v-for="(faq, index) in faq_items" :key="index"> <p>{{ faq.question }}</p> <p>{{ faq.answer }}</p> </div> </div> </template>

Your displayed result should look something like this:

Now anyone on your team can update the values from the ButterCMS dashboard and the corresponding content in your app will automatically update.

Example 3: Blog Posts

Last, we’ll tackle a blog engine for the app.

Step 1: Displaying Posts

We’ll start out by creating a blog route using vue-router. To display posts we create a simple /blog route in our app and fetch blog posts, as well as a /blog/:slug route to handle individual posts.

In router/index.js:

import Vue from 'vue' import Router from 'vue-router' import BlogHome from '@/components/BlogHome' import BlogPost from '@/components/BlogPost' Vue.use(Router) export default new Router({ mode: 'history', routes: [ { path: '/blog/', name: 'blog-home', component: BlogHome }, { path: '/blog/:slug', name: 'blog-post', component: BlogPost } ] }) Step 2: Creating the Blog Homepage

To create your blog homepage that displays the most recently published posts, you’ll create a Vue component for the blog home in a new components/BlogHome.vue file:

<script> import { butter } from '@/buttercms' export default { name: 'blog-home', data() { return { page_title: 'Blog', posts: [] } }, methods: { getPosts() { butter.post.list({ page: 1, page_size: 10 }).then((res) => { // console.log(res.data) this.posts = res.data.data }) } }, created() { this.getPosts() } } </script>

If you have been following along with the previous examples, then you may start to see a pattern here and know that you need to display the content by defining the template and calling the fields in the same component file:

<template> <div id="blog-home"> <h1>{{ page_title }}</h1> <div v-for="(post,index) in posts" :key="post.slug + '_' + index"> <router-link :to="'/blog/' + post.slug"> <article class="media"> <figure> <img v-if="post.featured_image" :src="post.featured_image" alt=""> <img v-else src="http://via.placeholder.com/250x250" alt=""> </figure> <h2>{{ post.title }}</h2> <p>{{ post.summary }}</p> </article> </router-link> </div> </div> </template>

Assuming your fields match the example, your blog homepage should look something like this:

Step 3: Creating a Blog Post

Next, create a new components/BlogPost.vue file which will be your view for a single post:

<script> import { butter } from '@/buttercms' export default { name: 'blog-post', data() { return { post: {} } }, methods: { getPost() { butter.post.retrieve(this.$route.params.slug) .then((res) => { // console.log(res.data) this.post = res.data }).catch((res) => { console.log(res) }) } }, created() { this.getPost() } } </script>

You may have guessed it, but now you need to define the template and make a call to the blog post content fields:

<template> <div id="blog-post"> <h1>{{ post.data.title }}</h1> <h4>{{ post.data.author.first_name }} {{ post.data.author.last_name }}</h4> <div v-html="post.data.body"></div> <router-link v-if="post.meta.previous_post" :to="/blog/ + post.meta.previous_post.slug" class="button"> {{ post.meta.previous_post.title }} </router-link> <router-link v-if="post.meta.next_post" :to="/blog/ + post.meta.next_post.slug" class="button"> {{ post.meta.next_post.title }} </router-link> </div> </template>

Again, assuming you're using similar fields as the example, this is what you might expect to see:

Step 4: Handling Blog Post Routes

At this point, your app is pulling all blog posts, allowing you to navigate to individual posts. But, you will notice the next/previous buttons in the browser aren’t working. Why? When using routes with params, the same component instance will be reused when the user navigates from /blog/foo to /blog/bar.

Since both routes render the same component, this is more efficient than destroying the old instance and creating a new one. But, this also means that the lifecycle hooks of the component will not be called.

There is a fix for this. We need to watch the $route object and call getPost() when the route changes. To do this, update the script section in components/BlogPost.vue:

<script> import { butter } from '@/buttercms' export default { name: 'blog-post', data() { return { post: {} } }, methods: { getPost() { butter.post.retrieve(this.$route.params.slug) .then((res) => { // console.log(res.data) this.post = res.data }).catch((res) => { console.log(res) }) } }, watch: { $route(to, from) { this.getPost() } }, created() { this.getPost() } } </script>

At this point, your app has a working blog that can be updated easily from the CMS dashboard.

You can also use APIs to filter and feature content on your blog with categories, tags, and authors. In fact, there’s a lot you can do with an API in terms of managing different aspects of your blog, including RSS, Atom feeds, sitemap markup, and content styling with CSS.

Wrapping Up

Congrats! You’ve built a serverless Vue.js application with performant content API and are now equipped with real-life use cases for content you might expect to see on any given marketing site. Your development team can get back to coding and non-technical members of your team can have an easy way to manage content without any hand-holding through the code base. And, best yet, this your app is powered by dynamic content that will can be adapted, re-purposed and scaled for future needs.

The post Building a Serverless CMS Powered by Vue.js appeared first on CSS-Tricks.

Third party CSS is not safe

Css Tricks - Thu, 03/01/2018 - 12:40pm

...because third-party anything really isn't safe. Jake Archibald:

If you're worried about users tricking your site into loading third party resources, you can use CSP as a safety net, to limit where images, scripts and styles can be fetched from.

We've long discussed security considerations for using and managing third-party scripts, but the topic of security in third-party CSS was recently broached in response to a "trick" that employs keylogging via CSS.

Jake's post is a worthy read because it takes a high-level look at all third-party assets and the risks they pose.

Direct Link to ArticlePermalink

The post Third party CSS is not safe appeared first on CSS-Tricks.

WordPress Comment Spam

Css Tricks - Thu, 03/01/2018 - 4:15am

Akismet is an incredible spam preventer for WordPress sites. I'd say it does 95% of the work for us. A few issues though make me want to augment it with other tools:

  1. Some spam still slips through
  2. It doesn't prevent spam that seems easy to block
  3. There are false-positives, so spam still needs to be checked

#1 is no big deal, we can nuke the slips pretty easily. We even have WordPress comment settings such that all comments need to be manually approved these days, so those that slip through need to be moderated anyway, so never see the light of day.

Here's an example of #2:

We get enough of that that it's pretty obnoxious. A few hundred per week. And because of #3, that means sifting through loads of crap to make sure no real comment is lost in the junk.

I used the Pro version of the Anti-spam plugin. That plugin page doesn't inspire a ton of confidence, but I used it for years and it worked pretty well. Again, it's weird to run two spam plugins, but Akismet and Anti-spam seemed to work together well. Anti-spam added a bit of extra protection:

The blocking algorithm is based on 2 methods: ‘invisible js-captcha’ and ‘invisible input trap’ (aka honeypot technique).

But unfortunately, I had to disable it. We flipped on Jetpack comments because I liked the idea of having a comment form that allows for social login. The idea of typing in your name and email and all that is so old school that it's a turn off for a new generation of blog commenters. The fact that Jetpack offers that seems like an easy win. When Anti-spam was enabled, it must send some extra data or something bizarre that freaks out Jetpack, and it makes all comments throw an error when submitted.

With Anti-spam off, now we're flooded with the "easily blocked" style spam. Not the end of the world, but not ideal.

I wonder if other folks have had this issue and have what they consider a pretty sweet WordPress spam prevention system? Maybe some kind of honeypot technique that somehow doesn't screw up Jetpack Comments?

The post WordPress Comment Spam appeared first on CSS-Tricks.

?Learn UI Design: The Complete Video Course

Css Tricks - Thu, 03/01/2018 - 4:14am

(This is a sponsored post.)

If you’ve ever thought “Man, all my designs look like crap”, this may be the best ad you see all day. If you’ve desperately searched Dribbble or Behance for inspiration, yet found yourself completely unable to make something look nice, this one’s for you. And if you’ve ever had a sinking feeling that most design articles are worthless, and no matter how much you read about color theory, it’s not going to make your bad designs look good, well, let’s talk.

Learn UI Design is an online video course to take you from design newbie to being able to confidently create beautiful designs for any site or app. From color to typography, icons to process, Learn UI Design covers every aspect of interface design. Enrollment is open for 2 weeks only.

I should introduce myself. I’m Erik Kennedy. I’m an independent designer, I’ve traveled the globe designing sites and apps for companies big and small (like Soylent and Amazon), and my design writing has been read by over a million people (you might know me from this article). Yet I started out as a developer who couldn’t create nice-looking software to save his life. Sure, I developed some applications for work, created a few websites and side projects at home, even tried my hand at a nights-and-weekend startup. But there was an issue: everything I made looked like crap.

Design was something I was always interested in, but never great at. I knew what I liked, but I didn’t know how to create such a design. Consequently, everything I did had One-Man-Project syndrome: it looked like it was made by someone in their spare time – not professional, not considered, not worth the download, not worth the purchase.

In the end, I learned design the same way I’ve learned any creative endeavor: cold, hard analysis. And shameless copying of what’s worked. I’ve worked 10 hours on a UI project and billed for 1. The other 9 were the wild flailing of learning. Desperately searching Dribbble and Behance and Pinterest for some idea of how to make my awful design better.

That was the beginning, anyhow. Over time, I built up a toolset of hacks and heuristics. I was tired of reading design articles that failed the fundamental test of any skill tutorial: it didn’t help me improve what I was working on then and there. My gold standard was to find what worked. What made a difference between ugly and gorgeous. Over the years, I built up these tools across all areas of user interface design – color, typography, iconography, and so on.

Today, Learn UI Design has hundreds of happy students, and the course is used and regarded by folks like Chris Coyier…

I've been watching @erikdkennedy's Learn UI Design course to bone up a bit. He's kinda like the @wesbos of design.https://t.co/MUkcNmYdMN pic.twitter.com/QSbXvITdKG

— Chris Coyier (@chriscoyier) February 6, 2018

…and Jeremiah Shoaf (the founder of Typewolf).

And of course plenty of other mere mortals:

Here’s a peek at the syllabus:

I. INTRODUCTION
  • Begin here (11:10)
  • Setting Up Sketch & Asset Files for UI Design (15:36)
  • How to Build Your Design Gut Instinct (16:26)
  • 3 Methods for Designing Above Your Level (10:44)
  • Finding & Using Design Inspiration (20:46)
II. UI FUNDAMENTALS
  • Analyzing Aesthetics (17:18)
  • Alignment (36:32)
  • Spacing (52:12)
  • Lighting & Shadows (32:28)
  • Grids (25:37)
  • Consistency (34:19)
III. COLOR
  • Introduction to HSB (13:32)
  • Luminosity (20:00)
  • Gray: The Most Important Color (27:32)
  • Adjustment: The Most Important Color Skill (34:46)
  • 3 Ways to Fix Clashing Colors (9:09)
  • Picking a Primary UI Color (10:33)
  • Picking Secondary UI Colors (47:04)
  • Dark Interfaces (22:41)
  • Gradients (27:15)
IV. TYPOGRAPHY
  • Terminology: The Bare Minimum
  • Choosing Fonts (53:42)
  • Good Fonts Table
  • Styling Text (44:24)
  • Styling Text 2 (35:56)
  • Pairing Fonts (50:35)
  • 7 Methods for Overlaying Text on Images (21:13)
V. USER INTERFACE COMPONENTS
  • Form Controls (42:38)
  • Icons 1: Vector Editing (30:46)
  • Icons 2: Icon Design (52:39)
  • Photography & Imagery (39:34)
  • Lists & Tables (41:49)
VI. REAL-WORLD PROCESS
  • Responsive UI Design (48:54)
  • Designing Multi-State Screens (38:32)
  • Creating a Design Portfolio (33:07)
  • Finding Clients (18:29)
  • Presenting & Getting Good Feedback on Your Designs (34:17)

For those of you keeping score at home, that’s 35 videos totaling almost 20 hours of content.

Sign up now and get:

  • Immediate access to the full video curriculum
  • Dozens of multimedia resources, downloads, and homework assignments
  • Access to the Learn UI Design Slack community, where you can get feedback, design reviews, and more design resources

Comes with a no-questions-asked, 30-day money back guarantee.

Learn UI Design is open for enrollments now through March 14th.

» Enroll now

Direct Link to ArticlePermalink

The post ?Learn UI Design: The Complete Video Course appeared first on CSS-Tricks.

Responsive Components: a Solution to the Container Queries Problem

Css Tricks - Wed, 02/28/2018 - 10:40am

Container Queries, as in, the ability to style elements based on values from a particular element, like its width and height. We have media queries, but those are based on the viewport not individual elements. There are plenty of use cases for them. It's been said before, but I'll say it again, if container queries existed, the vast majority of media queries in CSS would actually be container queries.

Discussion about how to pull it off technologically gets interesting. In my mind, ideally, we get this ability right in CSS. The trouble with doing it this way is one of circularity. Not even in regards to being able to write CSS that triggers a scenario in which the query doesn't match anymore, which is tricky enough, but literally changing the long-standing single-pass way in which browsers render a page.

The trouble with container queries isn't finding the perfect syntax or convincing anyone they are needed, it's how browsers work.https://t.co/7ZxMczD4ag pic.twitter.com/DeHyR9zRuO

— CSS-Tricks (@Real_CSS_Tricks) February 1, 2018

There are plenty of takes at solving this. All JavaScript of course. Dave Rupert rounded some of them up here. They are all a bit different.

Seems to me the most well-suited JavaScript API for this is ResizeObserver. It's Chrome-only as I write, but here's a chart that should stay updated in time:

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

DesktopChromeOperaFirefoxIEEdgeSafari64NoNoNoNoNoMobile / TabletiOS SafariOpera MobileOpera MiniAndroidAndroid ChromeAndroid FirefoxNoNoNoNoNoNo

That was a heck of a lot of words to intro the fact that Philip Walton just wrote a hell of an article about doing just this. The core of it is that you use ResizeOveserver to toss classes onto elements, then style them with those classes. He concludes:

The strategy outlined in this article:

  • Will work, today, on any website
  • Is easy to implement (copy/paste-able)
  • Performs just as well as a CSS-based solution
  • Doesn’t require any specific libraries, frameworks, or build tools.
  • Leverages progressive enhancement, so users on browser that lack the required APIs or have JavaScript disabled can still use the site.

The browser support for ResizeObserver is a little scary, but it's such a nice API I would expect more widespread support sooner than later.

Direct Link to ArticlePermalink

The post Responsive Components: a Solution to the Container Queries Problem appeared first on CSS-Tricks.

Using Sass to Control Scope With BEM Naming

Css Tricks - Wed, 02/28/2018 - 4:09am

Controlling scope is something you probably don't tend to consider when working with CSS and Sass. We have had access to the ampersand (&) for quite some time now, which gives us a level of scope—but it's easy for it to lose its usefulness when you're nested quite deeply. The & can send us down a windy road of doom when we completely lose track of what it means and can be responsible for some really heavy bloat.

Meanwhile, in JavaScript, we can control the scope of this by casting it as a variable at the point where it means what we will want it to. This is often at the start of a method or object:

function foo() { let self = this; return function() { // Self = foo(), even int his closure return self; } } // We need to instantiate foo() or its 'this' will be the window let bar = new foo();

Now that we have self in the above context, we always have a reference to foo() regardless of where we might find ourselves within the function. This is really useful when we end up in setTimeout scenarios, closures or event handlers.

A quick example of an event will hopefully help to illustrate my point.

Using this markup:

<div class="component"> <div class="component__child-element"></div> </div> <div class="component"> <div class="component__child-element"></div> </div>

We can add this JavaScript:

function foo() { // Load up all component child elements let childElements = [...document.querySelectorAll('.component__child-element')]; // Loop each element and add an event listener childElements.map(element => { element.addEventListener('click', function(evt) { // Log what `this` currently is console.log(this); }); }); } // Create a new instance of foo let bar = new foo();

In that code sample, if you click a component__child-element with your console open, this will report itself as the event target, which happens to be the element that we clicked. This isn't ideal if you thought it would reference foo().

Now, if we run the same sample with self in place of this in the event handler, the console will report an instance of foo() instead:

function foo() { // Control scope of this by storing it let self = this; // Load up all component child elements let childElements = [...document.querySelectorAll('.component__child-element')]; // Loop each element and add an event listener childElements.map(element => { element.addEventListener('click', function(evt) { // Self will report itself as `foo()` console.log(self); }); }); } // Create a new instance of foo let bar = new foo(); So, how does this relate to Sass?

I'm glad you stuck with me there, because that JavaScript example was a primer for what we're going to look at with Sass.

First, let's start with that same markup and some core styles.

<div class="component"> <div class="component__child-element"></div> </div> <div class="component"> <div class="component__child-element"></div> </div> .component { display: block; max-width: 30rem; min-height: 30rem; margin: 5rem auto; background: rebeccapurple; position: relative; border: 1px dashed rebeccapurple; &__child-element { display: block; width: 15rem; height: 15rem; border-radius: 50%; position: absolute; top: 50%; left: 50%; transform: translate3d(-50%, -50%, 0); background: white; } }

This gives us a nice purple square with a white circle inside it. Nothing groundbreaking, but useful for this example.

See the Pen Square with Circle by Geoff Graham (@geoffgraham) on CodePen.

You'll probably notice that we're using BEM syntax, so let's go ahead and create a modifier.

We want an inverted version of .component that has a white background with a purple circle in it. So, let's go ahead and approach it how we'd probably think to do it straight away by including it in the same nested ruleset:

.component { // Other styles redacted for brevity // The reversed modifier flips the colors around &--reversed { background: white; border-color: lightgray; &__child-element { background: rebeccapurple; } } }

See the Pen Square with Circle by Geoff Graham (@geoffgraham) on CodePen.

Wait, why is this not working? The problem is that the & has a scope of .component--reversed, so &__child-element compiles to .component--reversed__child-element, which doesn't exists in the markup.

$self to the rescue!

Just like in the JavaScript we looked at before, we're going to cast our initial scope into a variable called $self. You can do it like this:

.component { $self: &; }

That means that wherever we use $self in our component, it will compile to .component.

So let's refactor that reversed modifier, so that it actually works:

.component { $self: &; // Hey look, it's our new best friend! display: block; max-width: 30rem; min-height: 30rem; // Other styles redacted &--reversed { background: white; border-color: lightgray; // Here, we use $self to get the correct scope #{ $self }__child-element { background: rebeccapurple; } } }

The compiled CSS for that element is now .component--reversed .component__child-element which of course exists and successfully turns the inner circle purple:

See the Pen 'Using Sass to Control Scope With BEM Naming' example by Andy Bell (@hankchizljaw) on CodePen.

Further exploration

One thing I like to do in my components is reference a parent's modifier within the element itself, rather than referencing the element within the modifier like we did earlier. This is to keep my styles grouped per element. For the same reason, I'll also put media queries within elements too.

I end up with code that looks like this:

// We're still within .component where $self has been declared. &__child-element { display: block; width: 15rem; height: 15rem; border-radius: 50%; position: absolute; top: 50%; left: 50%; transform: translate3d(-50%, -50%, 0); background: white; // Flip the color when the parent is reversed #{ $self }--reversed & { background: rebeccapurple; } }

Having access to $self as the root context gives us the flexibility to approach our modifiers like that with ease. Because $self is .component and & is .component__element, adding the --reversed portion generates us .component--reversed .component__element which is exactly what we wanted.

See the Pen 'Using Sass to Control Scope With BEM Naming' example by Andy Bell (@hankchizljaw) on CodePen.

A more advanced example

Now let's really push the boat out and write something interesting. We're going to layout three separate grids. Using $self, I'm going to change the color of the grid items using a sibling selector within a grid-item itself.

See the Pen 'Using Sass to Control Scope With BEM Naming' example by Andy Bell (@hankchizljaw) on CodePen.

Let's look at the Sass that affects the color:

&__item { // Item styles redacted for brevity // For each direct `.grid sibling`, make this item rebeccapurple #{ $self } + #{ $self } & { background: rebeccapurple; } }

What that selector compiles to is .grid + .grid .grid__item. The combination of $self and & enables us to generate that within the context of the element which is our &. This is incredibly powerful for maintaining some sort of order within complex codebases.

Wrapping up

We've taken inspiration from JavaScript to control scope in our BEM components with Sass by using this little snippet at the root: $self: &. With the scope control, we gain flexibility to write clean, organized, BEM driven CSS when we are deep in a modifier or child element.

I hope this little tip will help you to improve your Sass. If it does, get in touch with me on Twitter or drop a comment below. It'll be great to see how gaining control of scope with Sass has helped you out.

Using Sass to Control Scope With BEM Naming is a post from CSS-Tricks

Everything you need to know about CSS Variables

Css Tricks - Tue, 02/27/2018 - 9:27am

This is by far the biggest deep dive I've seen on CSS Variables posted to the web and it's merely Chapter One of complete e-book on the topic.

Truth is, I'm still on the thick of reading through this myself, but had to stop somewhere in the middle to write this up and share it because it's just that gosh-darned useful. For example, the post goes into great detail on three specific use cases for CSS Variables and breaks the code down to give a better understanding of what it does, in true tutorial fashion.

Scoping, inheritance, resolving multiple declarations, little gotchas—there's plenty in here for beginners and advanced developers alike.

OK, back to reading. &#x1f913;

Direct Link to ArticlePermalink

Everything you need to know about CSS Variables is a post from CSS-Tricks

Let’s Build a Custom Vue Router

Css Tricks - Tue, 02/27/2018 - 4:21am

Plenty of tutorials exist that do a great job in explaining how Vue’s official routing library, vue-router, can be integrated into an existing Vue application. vue-router does a fantastic job by providing us with the items needed to map an application’s components to different browser URL routes.

But, simple applications often don’t need a fully fledged routing library like vue-router. In this article, we'll build a simple custom client-side router with Vue. By doing so, we’ll gather an understanding of what needs to be handled to construct client-side routing as well as where potential shortcomings can exist.

Though this article assumes basic knowledge in Vue.js; we'll be explaining things thoroughly as we start to write code!

Routing

First and foremost: let’s define routing for those who may be new to the concept.

In web development, routing often refers to splitting an application’s UI based on rules derived from the browser URL. Imagine clicking a link and having the URL go from https://website.com to https://website.com/article/. That’s routing.

Routing is often categorized in two main buckets:

  • Server-side routing: the client (i.e. the browser) makes a request to the server on every URL change.
  • Client-side routing: the client only makes a request to the server upon initial-page load. Any changes to the application UI based on URL routes are then handled on the client.

Client-side routing is where the term single-page application (or SPA for short) comes in. SPAs are web apps that load only once and are dynamically updated with user interaction without the need to make subsequent requests to the server. With routing in SPAs, JavaScript is the driving force that dynamically renders different UI.

Now that we have a brief understanding of client-side routing and SPAs, let’s get an overview of what we’ll be working on!

Case Study: Pokémon

The app we aim to construct is a simple Pokémon app that displays details of a particular Pokémon based on the URL route.

The application will have three unique URL routes: /charizard, /blastoise, and /venusaur. Based on the URL route entered, a different Pokémon will be shown:

In addition, footer links exist at the bottom of the application to direct the user to each respective route upon click:

Do We Even Need Routing for This?

For simple applications like this, we don’t necessarily need a client-side router to make our app functional. This particular app could be composed of a simple parent-child component hierarchy that uses Vue props to dictate the information that should be displayed. Here’s a Pen that shows just this:

See the Pen Vue Pokemon by Hassan Dj (@itslit) on CodePen.

Though the app would functionally work, it misses a substantial feature that’s expected from most web applications—responding to browser navigation events. We’d want our Pokémon app to be accessible and to show different details for different pathnames: /charizard, /blastoise, and /venusaur. This would allow users to refresh different pages and keep their location in the app, bookmark the URLs to come back to later, and potentially share the URL with others. These are some of the main benefits of creating routes within an application.

Now that we have an idea of what we’ll be working on, let’s start building!

Preparing the App

The easiest way to follow along step-by-step (if you wish to do so) is to clone the GitHub repo I've set up.

Download on GitHub

When cloned, install the project dependencies with:

npm install

Let’s take a brief look within the project directory.

$ ls README.md index.html node_modules/ package.json public/ src/ static/ webpack.config.js

There also exists the hidden files, .babelrc and .gitignore within the project scaffold.

This project is a simple webpack-configured application scaffolded with vue-cli, the Vue command line interface.

index.html is where we declare the DOM element—#app— with which we'll use to mount our Vue application:

<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.5.3/css/bulma.css"> <link rel="stylesheet" href="../public/styles.css" /> <title>Pokémon - Routing</title> </head> <body> <div id="app"></div> <script src="/dist/build.js"></script> </body> </html>

In the <head> tag of the index.html file, we introduce Bulma as our application’s CSS framework and our own styles.css file that lives in the public/ folder.

Since our focus is on the usage of Vue.js, the application already has all the custom CSS laid out.

The src/ folder is where we’ll be working directly from:

$ ls src/ app/ main.js

src/main.js represents the starting point of our Vue application. It’s where our Vue instance is instantiated, where we declare the parent component that is to be rendered, and the DOM element #app with which our app is to be mounted to:

import Vue from 'vue'; import App from './app/app'; new Vue({ el: '#app', render: h => h(App) });

We’re specifying the App component, from the src/app/app.js file, to be the main parent component of our application.

In the src/app directory, there exists two other files - app-custom.js and app-vue-router.js:

$ ls src/app/ app-custom.js app-vue-router.js app.js

app-custom.js denotes the completed implementation of the application with a custom Vue router (i.e. what we'll be building in this article). app-vue-router.js is a completed routing implementation using the vue-router library.

For the entire article, we’ll only be introducing code to the src/app/app.js file. With that said, let’s take a look at the starting code within src/app/app.js:

const CharizardCard = { name: 'charizard-card', template: ` <div class="card card--charizard has-text-weight-bold has-text-white"> <div class="card-image"> <div class="card-image-container"> <img src="../../static/charizard.png"/> </div> </div> <div class="card-content has-text-centered"> <div class="main"> <div class="title has-text-white">Charizard</div> <div class="hp">hp 78</div> </div> <div class="stats columns is-mobile"> <div class="column">&#x1f525;<br> <span class="tag is-warning">Type</span> </div> <div class="column center-column">199 lbs<br> <span class="tag is-warning">Weight</span> </div> <div class="column">1.7 m <br> <span class="tag is-warning">Height</span> </div> </div> </div> </div> ` }; const App = { name: 'App', template: ` <div class="container"> <div class="pokemon"> <pokemon-card></pokemon-card> </div> </div> `, components: { 'pokemon-card': CharizardCard } }; export default App;

Currently, two components exist: CharizardCard and App. The CharizardCard component is a simple template that displays details of the Charizard Pokémon. The App component declares the CharizardCard component in its components property and renders it as <pokemon-card></pokemon-card> within its template.

We currently only have static content with which we’ll be able to see if we run our application:

npm run dev

And launch localhost:8080:

To get things started, let’s introduce two new components: BlastoiseCard and VenusaurCard that contains details of the Blastoise and Venusaur Pokémon respectively. We can lay out these components right after CharizardCard:

const CharizardCard = { // ... }; const BlastoiseCard = { name: 'blastoise-card', template: ` <div class="card card--blastoise has-text-weight-bold has-text-white"> <div class="card-image"> <div class="card-image-container"> <img src="../../static/blastoise.png"/> </div> </div> <div class="card-content has-text-centered"> <div class="main"> <div class="title has-text-white">Blastoise</div> <div class="hp">hp 79</div> </div> <div class="stats columns is-mobile"> <div class="column">&#x1f4a7;<br> <span class="tag is-light">Type</span> </div> <div class="column center-column">223 lbs<br> <span class="tag is-light">Weight</span> </div> <div class="column">1.6 m<br> <span class="tag is-light">Height</span> </div> </div> </div> </div> ` }; const VenusaurCard = { name: 'venusaur-card', template: ` <div class="card card--venusaur has-text-weight-bold has-text-white"> <div class="card-image"> <div class="card-image-container"> <img src="../../static/venusaur.png"/> </div> </div> <div class="card-content has-text-centered"> <div class="main"> <div class="title has-text-white">Venusaur</div> <div class="hp hp-venusaur">hp 80</div> </div> <div class="stats columns is-mobile"> <div class="column">&#x1f343;<br> <span class="tag is-danger">Type</span> </div> <div class="column center-column">220 lbs<br> <span class="tag is-danger">Weight</span> </div> <div class="column">2.0 m<br> <span class="tag is-danger">Height</span> </div> </div> </div> </div> ` }; const App = { // ... }; export default App;

With our application components established, we can now begin to think how we’ll create routing between these components.

router-view

To establish routing, we’ll start by buiding a new component that holds the responsibility to render a specified component based on the app’s location. We’ll create this component in a constant variable named View.

Before we create this component, let’s see how we might use it. In the template of the App component, we’ll remove the declaration of <pokemon-card> and instead render the upcoming router-view component. In the components property; we’ll register the View component constant as <router-view> to be declared in the template.

const App = { name: 'App', template: ` <div class="container"> <div class="pokemon"> <router-view></router-view> </div> </div> `, components: { 'router-view': View } }; export default App;

The router-view component will match the correct Pokémon component based on the URL route. This matching will be dictated in a routes array that we’ll create. We’ll create this array right above the App component:

const CharizardCard = { // ... }; const BlastoiseCard = { // ... }; const VenusaurCard = { // ... }; const routes = [ {path: '/', component: CharizardCard}, {path: '/charizard', component: CharizardCard}, {path: '/blastoise', component: BlastoiseCard}, {path: '/venusaur', component: VenusaurCard} ]; const App = { // ... }; export default App;

We’ve set each Pokémon path to their own respective component (e.g. /blastoise will render the BlastoiseCard component). We’ve also set the root path / to the CharizardCard component.

Let’s now begin to create our router-view component.

The router-view component will essentially be a mounting point to dynamically switch between components. One way we can do this in Vue is by using the reserved <component> element to establish Dynamic Components.

Let’s create a starting point for router-view to get an understanding of how this works. As mentioned earlier; we’ll create router-view within a constant variable named View. So with that said, let’s set up View right after our routes declaration:

const CharizardCard = { // ... }; const BlastoiseCard = { // ... }; const VenusaurCard = { // ... }; const routes = [ // ... ]; const View = { name: 'router-view', template: `<component :is="currentView"></component>`, data() { return { currentView: CharizardCard } } }; const App = { // ... }; export default App;

The reserved <component> element will render whatever component the is attribute is bound to. Above, we’ve attached the is attribute to a currentView data property that simply maps to the CharizardCard component. As of now, our application resembles the starting point by displaying CharizardCard regardless of what the URL route is.

Though router-view is now appropriately rendered within App, it’s not currently dynamic. We need router-view to display the correct component based on the URL pathname upon page load. To do this, we’ll use the created() hook to filter the routes array and return the component that has a path that matches the URL path. This would make View look something like this:

const View = { name: 'router-view', template: `<component :is="currentView"></component>`, data() { return { currentView: {} } }, created() { this.currentView = routes.find( route => route.path === window.location.pathname ).component; } };

In the data function, we’re now instantiating currentView with an empty object. In the created() hook, we’re using JavaScript’s native find() method to return the first object from routes that matches route.path === window.location.pathname. We can then get the component with object.component (where object is the returned object from find()).

Inside a browser environment, window.location is a special object containing the properties of the browser’s current location. We grab the pathname from this object which is the path of the URL.

At this stage; we'll be able to see the different Pokémon Card components based on the state of our browser URL!

The BlastoiseCard component now renders at the /blastoise route.

There’s something else we should consider. If a random URL pathname is entered, our app will currently error and present nothing to the view.

To avoid this, let’s introduce a simple check to display a "Not Found" template if the URL pathnamedoesn’t match any path existing in the routes array. We’ll separate out the find() method to a component method named getRouteObject() to avoid repetition. This updates the View object to:

const View = { name: 'router-view', template: `<component :is="currentView"></component>`, data() { return { currentView: {} } }, created() { if (this.getRouteObject() === undefined) { this.currentView = { template: ` <h3 class="subtitle has-text-white"> Not Found :(. Pick a Pokémon from the list below! </h3> ` }; } else { this.currentView = this.getRouteObject().component; } }, methods: { getRouteObject() { return routes.find( route => route.path === window.location.pathname ); } } };

If the getRouteObject() method returns undefined, we display a "Not Found" template. If getRouteObject()returns an object from routes, we bind currentView to the component of that object. Now if a random URL is entered, the user will be notified:

The "Not Found" view is rendered if the URL pathname does not match any of the values in the routes array.

The "Not Found" template tells the user to pick a Pokémon from a list. This list will be the links we’ll create to allow the user to navigate to different URL routes.

Awesome! Our app is now responding to some external state, the location of the browser. router-view determines which component should be displayed based on the app’s location. Now, we need to construct links that will change the location of the browser without making a web request. With the location updated, we want to re-render our Vue app and rely on router-view to appropriately determine which component to render.

We’ll label these links as router-link components.

router-link

In web interfaces, we use HTML <a> tags to create links. What we want here is a special type of <a> tag. When the user clicks on this tag, we’ll want the browser to skip its default routine of making a web request to fetch the next page. Instead, we just want to manually update the browser’s location.

Let’s compose a router-link component that produces an <a> tag with a special click binding. When the user clicks on the router-link component, we’ll use the browser’s history API to update the browser’s location.

Just like we did with router-view, let’s see how we’ll use this component before we build it.

In the template of the App component, let’s create three <router-link> elements within a parent <div class="pokemon-links"></div> element. Rather than using the href attribute in <router-link>, we’ll specify the desired location of the link using a to attribute. We’ll also register the upcoming router-link component (from a Link constant variable) in the App components property:

const App = { name: 'App', template: ` <div class="container"> <div class="pokemon"> <router-view></router-view> <div class="pokemon-links has-text-centered"> <router-link to="/charizard"></router-link> <router-link to="/blastoise"></router-link> <router-link to="/venusaur"></router-link> </div> </div> </div> `, components: { 'router-view': View, 'router-link': Link } };

We’ll create the Link object that represents router-link right above the App component. We've established the router-link component should always be given a to attribute (i.e. prop) that has a value of the target location. We can enforce this prop validation requirement like so:

const CharizardCard = { // ... }; const BlastoiseCard = { // ... }; const VenusaurCard = { // ... }; const routes = [ // ... ]; const View = { // ... }; const Link = { name: 'router-link', props: { to: { type: String, required: true } } }; const App = { // ... }; export default App;

We can create the template of router-link to consist of an <a> tag with an @click handler attribute. Upon trigger, the @click handler will call a component method, labeled navigate(), that navigates the browser to the desired location. This navigation will occur with the use of the history.pushState() method. With that said, the Link constant object will be updated to:

const Link = { name: 'router-link', props: { to: { type: String, required: true } }, template: `<a @click="navigate" :href="to">{{ to }}</a>`, methods: { navigate(evt) { evt.preventDefault(); window.history.pushState(null, null, this.to); } } };

Within the <a> tag, we’ve bound the value of the to prop to the element text content with {{ to }}.

When navigate() is triggered, it first calls preventDefault() on the event object to prevent the browser from making a web request for the new location. The history.pushState() method is then called to direct the user to the desired route location. history.pushState() takes three arguments:

  • a state object to pass serialized state information
  • a title
  • the target URL

In our case, there is no state information that’s needed to be passed, so we've left the first argument as null. Some browsers (e.g. Firefox) currently ignore the second parameter, title, hence we’ve left that as null as well.

The target location, the to prop, is passed in to the third and last parameter. Since the to prop contains the target location in a relative state, it will be resolved relative to the current URL. In our case, /blastoise will resolve to http://localhost:8080/blastoise.

If we click any of the links now, we’ll notice our browser updates to the correct location without a full page reload. However, our app will not update and render the correct component.

This unexpected behaviour happens because when router-link is updating the location of the browser, our Vue app is not alerted of the change. We’ll need to trigger our app (or simply just the router-view component) to re-render whenever the location changes.

Though there’s a few ways to accomplish this behaviour, we’ll do this by using a custom EventBus. An EventBus is a Vue instance responsible in allowing isolated components to subscribe and publish custom events between each other.

At the beginning of the file, we’ll import the vue library and create an EventBus with a new Vue() instance:

import Vue from 'vue'; const EventBus = new Vue();

When a link has been clicked, we need to notify the necessary part of the application (i.e. router-view) that the user is navigating to a particular route. The first step is to create an event emitter using the EventBus's events interface in the navigate() method of router-link. We’ll give this custom event a name of navigate:

const Link = { // ..., methods: { navigate(evt) { evt.preventDefault(); window.history.pushState(null, null, this.to); EventBus.$emit('navigate'); } } };

We can now set the event listener/trigger in the created() hook of router-view. By setting the custom event listener outside of the if/else statement, the created() hook of View will be updated to:

const View = { // ..., created() { if (this.getRouteObject() === undefined) { this.currentView = { template: ` <h3 class="subtitle has-text-white"> Not Found :(. Pick a Pokémon from the list below! </h3> ` }; } else { this.currentView = this.getRouteObject().component; } // Event listener for link navigation EventBus.$on('navigate', () => { this.currentView = this.getRouteObject().component; }); }, // ... };

When the browser’s location changes by clicking a <router-link> element, this listening function will be invoked, re-rendering router-view to match against the latest URL!

Great! Our app now navigates appropriately as we click each of the links.

There’s one last thing we need to consider. If we try to use the browser back/forward buttons to navigate through the browser history, our application will not currently re-render correctly. Although unexpected, this occurs because no event notifier is emitted when the user clicks browser back or browser forward.

To make this work, we’ll use the onpopstate event handler.

The onpopstate event is fired each time the active history entry changes. A history change is invoked by clicking the browser back or browser forward buttons, or calling history.back() or history.forward() programmatically.

Right after our EventBus creation, let’s set up the onpopstate event listener to emit the navigate event when a history change is invoked:

window.addEventListener('popstate', () => { EventBus.$emit('navigate'); });

Our application will now respond appropriately even when the browser navigation buttons are used!

And there we have it! We’ve just built a custom Vue router using an EventBus and dynamic components. Even with the tiny size of our app we can enjoy a noticeable performance improvement. Avoiding a full page load also saves hundreds of milliseconds and prevents our app from "blinking" during the page change.

Conclusion

I love Vue. One reason as to why - it's incredibly easy to use and manipulate Vue components just like we saw in this article.

In the introduction, we mentioned how Vue provides the vue-router library as the official routing library of the framework. We’ve just created simple versions of the same main items that are used in vue-router:

  • routes: the array responsible in mapping components to respective URL pathnames.
  • router-view: the component that renders a specified app component based on the app’s location
  • router-link: the component that allows the user to change the location of the browser without making a web request.

For very simple applications, the routing we’ve built (or a variation thereof like this one built by Chris Fritz) can do the minimal amount of work needed to route our applications.

The vue-router library, on the other hand, is built in a more complicated manner and introduces incredibly useful capabilities, often needed in larger applications like:

Though the vue-router library does come with additional boilerplate, it’s fairly easy to integrate once your application is composed of well isolated and distinct components. If you're interested, you can see the components of vue-router being used to enable routing in this application here.

Hopefully this was as enjoyable to you as it was for me in compiling this post! Thanks for reading!

This article is an adapted (and summarized) segment from the upcoming book, Fullstack Vue, that I’m working on with the Fullstack.io team! Having the opportunity to work with the folks at Fullstack has been nothing short of being a blast. In true Fullstack fashion, the book covers numerous facets of Vue including but not restricted to routing, simple state management, form handling, Vuex, server persistence, and testing. If this is something that piques your interest or if you have any questions at all, follow (or message) me on twitter (@djirdehh)! If the above doesn’t pique your interest, you can still follow me anyway. &#x1f61b;

Let’s Build a Custom Vue Router is a post from CSS-Tricks

AMP News

Css Tricks - Mon, 02/26/2018 - 11:26am

AMP is controversial, to say the least. Dave and I did a show on it about a year ago that to me felt fairly balanced in addressing some of the issues. Let's cover some recent news and responses.

One thing that isn't usually controversial: it's fast. AMP pages are damn performant. Even that, though, is contentious. Ferdy Christant notes:

Technically correct AMP pages will perform very similar to any non-horrible web page.

The difference between AMP performing instantly and getting numbers ranging from 2–8s as seen above have to be explained.

Part of that answer you can probably guess: the cache is simply very fast. It’s hard to compete with a Google-class CDN.

You don't need AMP to have a fast website.

FYI @CityLab article pages load faster than @nytimes AMP pages. Just to show you do not need AMP to have fast loading pages #webperf pic.twitter.com/34YboEBwLP

— Michael Donohoe (@donohoe) February 23, 2018

The most controversial bit is that carrot offered for using AMP: the search results news carousel. The carousel is extremely prominent in Google search results, and AMP is a one-way ticket to get in. You could make a site faster than AMP, but that doesn't meet the criteria for entry. Tim Kadlec:

there has been no indication of any attempt to address the first issue, that of incentivization and premium placement. In fact, not only has there been no attempt to fix it, it appears the AMP team is doubling-down on those incentives instead.

Doubling-down, as in, AMP stories will be released soon and will also enjoy premium placement on Google. Every indication is that the primary desire of people reaching for AMP is the preferential search results treatment. Gina Trapani:

In my experience people are motivated to use AMP… I’ve seen this from our clients…mostly because of SEO. They want it in that top stories carousel, they want that lightning bolt of approval in regular search results which is happening now.

Of course, Google can do whatever they want. They are an independent company and if they wanna tell us that we have to use a special format to have benefits on their platform, then that's the way it is. It doesn't mean we have to be happy about it. Hundreds of people have signed the AMP letter, which calls for two changes:

  1. Instead of granting premium placement in search results only to AMP, provide the same perks to all pages that meet an objective, neutral performance criterion such as Speed Index. Publishers can then use any technical solution of their choice.
  2. Do not display third-party content within a Google page unless it is clear to the user that they are looking at a Google product. It is perfectly acceptable for Google to launch a “news reader” but it is not acceptable to display a page that carries only third-party branding on what is actually a Google URL, nor to require that third party to use Google’s hosting in order to appear in search results.

Ethan Marcotte is concerned:

absent action from some sort of regulatory body, I’m not sure what influence you or I could exert to change the trajectory of AMP

...but thinks we could perhaps collectively have influence. Jeremy Keith has called some of the messaging behind AMP an outright lie:

I don’t think the developers working on the AMP format are intentionally deceptive (although they are engaging in some impressive cognitive gymnastics). The AMP ecosystem, on the other hand, that’s another story—the preferential treatment of Google-hosted AMP pages in the carousel and in search results; that’s messed up.

Jeremy also notes that the power Google is exerting here is worrisome. Part of the stated motivation is trying to fix the web. Taking a stand, as it were.

I remember feeling very heartened to see WikiPedia, Google and others take a stand on January 18th, 2012 (against SOPA and PIPA). But I also remember feeling uneasy. In this particular case, companies were lobbying for a cause I agreed with. But what if they were lobbying for a cause I didn’t agree with? Large corporations using their power to influence politics seems like a very bad idea. Isn’t it still a bad idea, even if I happen to agree with the cause?

Cloudflare quite rightly kicked The Daily Stormer off their roster of customers. Then the CEO of Cloudflare quite rightly wrote this in a company-wide memo:

Literally, I woke up in a bad mood and decided someone shouldn’t be allowed on the Internet. No one should have that power.

There’s an uncomfortable tension here.

AMP is also expanding to other technology, notably email. Well, Gmail, that is. Fast, well-rendering, interactive emails seem like a hugely positive thing. Perhaps predictably at this point, people in that industry have similar concerns. Jason Rodriguez:

I’m an email guy. I’ve written three books on email, spoken at a bunch of conferences on the topic, and help build tools for other email folks at my day job. I love seeing the email platform grow and evolve. I love seeing people working on interesting ideas that make email more valuable for the subscribers that receive them.

So, you’d think I’d be thrilled by Google’s announcement about adding dynamic content and interactivity to Gmail with AMP for Email. You’d be wrong.

Jason's primary concern being that instead of improving support for what we already have, they've invented a new format and called it open-sourced, but have full control over it. However, with far more blockers in the way (e.g. ESPs not supporting the new MIME type) and less carrots to offer, it seems like a long shot it will happen.

I know I've covered a lot of negative news here, but that's mostly what I've been seeing. Strangely enough, I feel more interested in watching how this all shakes out than I am motivated to weigh in on either side.

AMP News is a post from CSS-Tricks

Syndicate content
©2003 - Present Akamai Design & Development.