Web Standards

5 Ways To Optimise Your App For High User Retention

Usability Geek - Wed, 01/16/2019 - 1:25pm
According to mobile app user retention rates from Statista, 55% of mobile app users will abandon an app one month after downloading it. That number grows to 68% after three months. Localytics gives...
Categories: Web Standards

How I’ve Been Using Notion Personally and Professionally

Css Tricks - Wed, 01/16/2019 - 12:53pm

I use Notion quite a bit, both personally and professionally.

In a sense, it's just an app for keeping documents in one place: little notes, to-do lists, basic spreadsheets, etc. I like the native macOS Notes app just fine. It's quick and easy, it's desktop and mobile, it syncs... but there are enough limitations that I wanted something better. Plus, I wanted something team-based and web-friendly (shared URLs!) and Notion hits those nails on the head.

Here's a bunch of ways to use Notion as well as some scattered random notes and ideas about it.

Workspaces are your teams

The word "workspace" almost makes it seem like you could or should use them as your top-level organization structure within a team. Like different projects would use different workspaces. But I'd say: don't do that. Workspaces are teams, even if it's a party of you and only you.

Pricing is billed by workspace. Team members are organized by workspace. Search is scoped by workspace. Switching workspaces isn't too difficult, but it's not lightning fast, either. I'd say it's worth honoring those walls and keeping workspaces to a minimum. It's almost like Slack. It's easy to get Too-Many-Slack'd, so best to avoid getting Too-Many-Notion'd.

Meeting notes

We have a weekly all-hands meeting at CodePen where we lay out what we've done and what we're going to do. It's nice to have that as a document so it can include links, notes, comments, embeds, etc.

Those notes don't disappear next week — we archive them as a historical record of everything we do.

Publishing and advertising schedules

I like looking at a spreadsheet-like document to see upcoming CSS-
Tricks articles with their statuses:

But that same exact document can be viewed as a calendar as well, if that's easier to look at:

There can be all sorts of views for the same table of content, which is a terrific feature:

Knowledge bases

This might be the easiest selling point for Notion. I'm sure a lot of companies have a whole bunch of documents that get into how the company works, including employee databases, coding guidelines, deployment procedures, dashboards to other software, etc. Sometimes that works as a wiki, but I've never seen a lovely setup for this kind of thing. Notion works fantastically as a collaborative knowledge base.

Public documents

I can make a document public, and even open it to public comments with the flip of a switch.

Another super fun thing in Notion is applying a header image and emoji, which gives each document a lot of personality. It's done in a way that can't really be screwed up and made into a gross-looking document. Here's an example where I've customized the page header — and look at the public URL!

I've also used that for job postings:

It's also great for things like public accessibility audits where people can be sent to a public page to see what kinds of things are being worked on with the ability to comment on items.

Quick, collaborative documents

Any document can be shared. I can create a quick document and share it with a particular person easily. That could be a private document shared only with team members, or with someone totally outside the team. Or I can make the document publicly visible to all.

I use Notion to create pages to present possibilities with potential sponsorship partners, then morph that into a document to keep track of everything we're doing together.

We use a show calendar for ShopTalk episodes. Each show added to the calendar automatically creates a page that we use as collaborative show notes with our guests.

It's worth noting that a Notion account is required to edit a document. So anyone that's invited will have to register and be logged in. Plus, you'll need to share it with the correct email address so that person can associate their account with that address.

Blog post drafts

I've been trying to keep all my blog post drafts in Notion. That way, they can easily be shared individually and I can browse all the collected ideas at once.

Exporting posts come out as Markdown too, and that's great for blog post drafts because it translates nicely:

Private sub-team documents

Being able to share documents with specific people is great. For example, we have a Founder's Meetings section on CodePen:

There are probably a million more things

Notion is simply documents with interesting blocks. Simple, but brilliant. For me, it can replace and consolidate things like Trello and other kanban tools, lightweight project management platforms, GitHub issues (if you don't need the public-ness), wikis, Google Docs and Spreadsheets, and even simple one-off public websites.

Here's a website dedicated to other people's interesting Notion pages.

What I want from Notion

I'd love to see Notion's web app links open in their desktop app instead of the web. I prefer the app because I have plenty of browser tabs open as it is. It's gotta be possible to do this with a browser extension. There used to be "Paws" for Trello and there was a browser extension that would open Trello links in that app, so there is prior precedent out there.

I'd also like Notion to explore a tabs feature. I constantly need more than one document open at a time and right now the only way to do that is with multiple windows. There is some back-and-forth arrow action that's possible, but it would be interesting to see native tabs or native window splitting somehow.

I'd love to see some performance upgrades too. It's not terribly bad, but working in Notion is so ubiquitous and important in my day-to-day that I'd love for it to feel more instantaneous than it currently does.

The post How I’ve Been Using Notion Personally and Professionally appeared first on CSS-Tricks.

Making Movies With amCharts

Css Tricks - Wed, 01/16/2019 - 5:44am

In this article, I want to show off the flexibility and real power of amCharts 4. We’re going to learn how to combine multiple charts that run together with animations that form a movie experience. Even if you’re only interested in creating a different kind of animation that has nothing to do with charts, you can still use this library, since it’s more than making charts. The core of amCharts is made to help with everything SVG: creation, layout, animation — basically a library that makes working with SVG fun!

Here’s the kind of thing I’m talking about. It's actually a demonstration of seven different charts animated together. We’ll walk through this together, covering how it works and how to re-create it so you can have amCharts in your tool belt the next time you’re working with charts or complex animations.

See the Pen React Hook: setEffect example by amCharts team (@amcharts) on CodePen.

First, let’s outline the stages of the movie

There’s a lot going on in the movie. Let’s break it up into digestible parts that allow us to parse out what’s going on and re-create those parts under the hood.

Here’s the gist of what we’re working with:

  1. The initial animations and states
  2. The pie chart pops up
  3. The pie chart morphs to a country
  4. The plane flies to some other country
  5. The plane becomes big and flies away
  6. The column chart appears and bends to a radar column chart
The initial animations and states

The first thing we’re hit is a pie chart rising from the depths with a curved line wrapped around it. There’s nothing special about the pie chart at this point, but we’ll cover it in the next section.

But what about that curved line? Remember, we make charts, so this line is simply a XY chart with a single line on it. All the other details — grid, labels, tick marks, etc. — are hidden. So, what we’re really looking at is a stripped-down line chart!

Setting up the line and pie chart animations

amCharts calls the lines on this chart a line series. A line series has a variable called tensionX and, in this case, it’s been set to 0.75, making for a curvy line. We have to think of tension like we would a rope that is being held by two people and both ends: the tighter the rope is pulled, the greater the tension; conversely, the tension gets looser as the two ends let up. That 0.75 value is a taking a quarter of a unit away from the initial value (1), creating less tension.

// Creates the line on the chart var lineSeries = lineChart.series.push(new am4charts.LineSeries()); lineSeries.dataFields.dateX = "date"; lineSeries.dataFields.valueY = "value"; lineSeries.sequencedInterpolation = true; lineSeries.fillOpacity = 0.3; lineSeries.strokeOpacity = 0; lineSeries.tensionX = 0.75; Loosens the tension to create a curved line lineSeries.fill = am4core.color("#222a3f"); lineSeries.fillOpacity = 1;

Initially, all the values of the series are the same: a flat line. Then, we set valueY value of the line’s animation to 80, meaning it pops up to the eights row of the chart height — that will make plenty of room for the pie when it comes in.

// Defines the animation that reveals the curved line lineSeries.events.on("shown", function(){ setTimeout(showCurve, 2000) }); // Sets the animation properties and the valueY so the line bounces up to // 80 on the chart's y-axis function showCurve() { lineSeries.interpolationEasing = am4core.ease.elasticOut; lineSeries.dataItems.getIndex(3).setValue("valueY", 80, 2000); setTimeout(hideCurve, 2000); } // This is the initial state where the line starts at 30 on the y-axis // before it pops up to 80 function hideCurve() { lineSeries.interpolationEasing = am4core.ease.elasticOut; lineSeries.dataItems.getIndex(3).setValue("valueY", 30, 2000); setTimeout(showCurve, 2000); }

Here is the line chart alone so we have a better visual for how that looks:

See the Pen deconstructing amCharts movie, stage 1 by amCharts team (@amcharts) on CodePen.

Next, the pie chart pops up from the bottom. Like a line series, amCharts includes a pie series and it has a dy property that we can set to hidden with a state of 400.

// Make the circle to show initially, meaning it will animate its properties from hidden state to default state circle.showOnInit = true; // Set the circle's easing and the duration of the pop up animation circle.defaultState.transitionEasing = am4core.ease.elasticOut; circle.defaultState.transitionDuration = 2500; // Make it appear from bottom by setting dy of hidden state to 300; circle.hiddenState.properties.dy = 300;

To illustrate this concept, here is a demo with that simple circle in place of a pie chart:

See the Pen deconstructing amCharts movie, initial animation by amCharts team (@amcharts) on CodePen.

A brief overview of amChart states

The idea of states in amCharts is this: you can have any number of custom states on any sprite. Then, instead of creating multiple animations with a slew of various different properties, state is changed from one to another and all the required properties that are set on the target state will animate from current values to the new state values.

Any numeric, percentage or color property of a sprite can be animated. By default, sprites have hidden and default states baked in. The hidden state is applied initially and followed by the revealed state, which is the default. There are other states, of course, like hover, active, disabled, among others, including custom states. Here is another demo showing a slice chart with innerRadius, radius and fill animating on hover:

See the Pen deconstructing amCharts movie, understanding states by amCharts team (@amcharts) on CodePen.

The pie chart pops up

Here is a demo of a basic pie chart. After some time, we’ll hide all the slices, except one, then show them all again.

See the Pen deconstructing amCharts movie, pie chart by amCharts team (@amcharts) on CodePen.

If you look at the code in the demo, you will see some of properties of the slices are customized via pieSeries.slices.template or pieSeries.labels.template. Most of the customization, of course, can be done using themes (amCharts 4 supports using multiple themes at the same time), but since we only need to change a few properties, we can use a template. We’re using a pie chart type and all of the slices of the pie series will be created using the provided template which passes any of the inherited properties we use from the template onto our pie chart.

// Call included themes for styling and animating am4core.useTheme(am4themes_amchartsdark); am4core.useTheme(am4themes_animated); // ... // Call the Pie Chart template var pieChart = mainContainer.createChild(am4charts.PieChart);

What if you want to set a custom color for the chart’s font? We can do this by adding a field in the data, like fontColor. That allows us to set custom colors there and then tell the label template that it should look at the field to inform the color property value.

// Define custom values that override one provided by the template pieChart.data = [{ "name": "[bold]No[/]", "fontColor": am4core.color("#222a3f"), "value": 220, "tickDisabled":true }, { "name": "Hm... I don't think so.", "radius":20, "value": 300, "tickDisabled":false }, { "name": "Yes, we are using amCharts", "value": 100, "labelDisabled": true, "tickDisabled":true }];

Any property of a sprite can be customized like this. And even later, after the chart is initialized, we can change any property via the template, or if we need to access some individual object, we can get any value using something like series.slices.getIndex(3) to isolate it.

To summarize: there isn't a single object on the chart that can’t be customized or accessed, changed, even after the chart is built. We’re working with a lot of flexibility!

The pie chart morphs into a country

I’ll be blunt: There is no way to morph a whole pie chart or some other complex object to the shape of a country. In amCharts 4, one polygon can morph into another one. And there are prebuilt methods that simply morph a polygon to a circle or to a rectangle. Here’s what we’ll do:

  • First, we hide all the slices of a pie chart, except one. This makes effectively transforms the remaining slice into a circle.
  • Then we animate the innerRadius property to 0, and the slice becomes a true circle.
  • There’s already a map chart at this moment, but it is hidden out of view. While it hides, we zoom into a selected country and morph it into a circle as well.
  • Next, we’ll show the country (which is now a circle) and hide the pie chart (which looks like the same circle at this time).
  • Finally, we morph the country back to its original shape.

Here is a simplified demo where we zoom in to the country, morph it to a circle, then morph it back to its default state:

See the Pen deconstructing amCharts movie, morphing by amCharts team (@amcharts) on CodePen.

Inspect that code. Note that all the methods we call, like zoomToMapObject, morphToCircle or morphBack, return an Animation object. An animation object dispatches events like animationstarted, animationprogress or animationended and we can attach listeners to them. This ensures that one animation is triggered only after another one is finished. And, if we change the duration of an animation, we won't need to adjust timers accordingly, because events will handle it. In amCharts 4, Sprites, Components, DataItems, Animations, and other objects have an event dispatcher object which regulate any events. You can add listeners for these events and use them to make your applications super interactive.

The plane flies from one country to another

At one point, an airplane surfaces at London on a map chart and travels all the way to Silicon Valley.

It might look complex and scary, but it’s using a lot of the concepts we’ve already covered and the features come standard with the map chart included in amCharts:

  • MapImageSeries is created and sprites (circles and labels) are mapped to the actual longitude latitude coordinates of both cities.
// Create first image container var imageSeries = mapChart.series.push(new am4maps.MapImageSeries()); // London properties var city1 = imageSeries.mapImages.create(); // London's latitude/longitude city1.latitude = 51.5074; city1.longitude = 0.1278; // prevent from scaling when zoomed city1.nonScaling = true; // New York City properties var city2 = imageSeries.mapImages.create(); // NY latitude/longitude city2.latitude = 40.7128; city2.longitude = -74.0060; // Prevent scaling when zoomed city2.nonScaling = true;
  • MapLineSeries, like the standard line series we saw earlier, creates a line between the cities based on the coordinates that are provided, going from one map image to another. By default, the line is drawn so that it follows the shortest distance between the objects. That happens to be a curved line in this case. We could make it a straight line if we’d like.
// Create the map line series var lineSeries = mapChart.series.push(new am4maps.MapLineSeries()); var mapLine = lineSeries.mapLines.create(); // Tell the line to connect the two cities (latitudes/longitudes an be used alternatively) mapLine.imagesToConnect = [city1, city2] // Draw the line in dashes mapLine.line.strokeDasharray = "1,1"; mapLine.line.strokeOpacity = 0.2;
  • An object (plane) is added to the MapLine and it moves between the endpoints of the line by animating the plane’s position property from 0 to 1.
// Create the plane container var planeContainer = mapLine.lineObjects.create(); planeContainer.position = 0; // Set the SVG path of a plane for the sprite var plane = planeContainer.createChild(am4core.Sprite); planeContainer.nonScaling = false; planeContainer.scale = 0.015; // SVG plane illustration plane.path = "M71,515.3l-33,72.5c-0.9,2.1,0.6,4.4,2.9,4.4l19.7,0c2.8,0,5.4-1,7.5-2.9l54.1-39.9c2.4-2.2,5.4-3.4,8.6-3.4 l103.9,0c1.8,0,3,1.8,2.3,3.5l-64.5,153.7c-0.7,1.6,0.5,3.3,2.2,3.3l40.5,0c2.9,0,5.7-1.3,7.5-3.6L338.4,554c3.9-5,9.9-8,16.2-8c24.2,0,85.5-0.1,109.1-0.2c21.4-0.1,41-6.3,59-17.8c4.2-2.6,7.9-6.1,11.2-9.8c2.6-2.9,3.8-5.7,3.7-8.5c0.1-2.8-1.1-5.5-3.7-8.5c-3.3-3.7-7-7.2-11.2-9.8c-18-11.5-37.6-17.7-59-17.8c-23.6-0.1-84.9-0.2-109.1-0.2c-6.4,0-12.3-2.9-16.2-8L222.6,316.6c-1.8-2.3-4.5-3.6-7.5-3.6l-40.5,0c-1.7,0-2.9,1.7-2.2,3.3L237,470c0.7,1.7-0.5,3.5-2.3,3.5l-103.9,0c-3.2,0-6.2-1.2-8.6-3.4l-54.1-39.9c-2.1-1.9-4.7-2.9-7.5-2.9l-19.7,0c-2.3,0-3.8,2.4-2.9,4.4l33,72.5C72.6,507.7,72.6,511.8,71,515.3z"; plane.fill = am4core.color("#eeeab5"); plane.horizontalCenter = "middle"; plane.verticalCenter = "middle";

Here is a demo of a plane flying from London to New York:

See the Pen deconstructing amCharts movie, map part by amCharts team (@amcharts) on CodePen.

Notice that the plane becomes bigger when it hits the line’s halfway point? This is done with three additional lines of code we can stick at the end.

// Make the plane to be bigger in the middle of the line planeContainer.adapter.add("scale", function(scale, target) { return (0.07 - 0.10 * (Math.abs(0.5 - target.position))) / mapChart.zoomLevel; })

We’re using a method that called an adapter, which is another super-powerful feature of amCharts 4. In this case, the adapter modifies the scale property (0.07 to 0.10), based on planes position (0.5).

The plane becomes big and flies away

When our plane reaches the target city (Silicon Valley in the full movie), we scale and rotate it to become horizontal and big.

At the same moment, we create another chart (the SlicedChart type) and add a PictorialSeries to it. The series share’s the same path as the plane, which creates a mask for the slices. We can use any SVG path here.

When the slices are shown, we want the plane to fly away:

This happens by animating the chart object’s dx property.

flyAway() function flyAway(){ var animation = pictorialChart.animate({property:"dx", to:2000}, 1500, am4core.ease.quadIn).delay(2000); animation.events.on("animationended", flyIn); } function flyIn(){ var animation = pictorialChart.animate({property:"dx", from:-2000, to:0}, 1500, am4core.ease.quadOut); animation.events.on("animationended", flyAway); }

Here is a demo of a Sliced chart:

See the Pen deconstructing amCharts movie, pictorial series by amCharts team (@amcharts) on CodePen.

The trail of a plane is again made with a line series, similar to the one we had in the beginning. This time, it's a combination of two separate series: one with positive and another with negative values. When a series has the sequencedInterpolation property set to true, the animation happens with some delay for each data value and we get an effect like this:

See the Pen deconstructing amCharts movie, plane trail by amCharts team (@amcharts) on CodePen.

var series1 = chart.series.push(new am4charts.LineSeries()) series1.dataFields.dateX = "date"; series1.dataFields.valueY = "value1"; series1.sequencedInterpolation = true; series1.fillOpacity = 1; series1.tensionX = 0.8; series1.stroke = am4core.color("#222a3f") series1.fill = am4core.color("#222a3f")

Then there’s the silhouette of a cityscape that scrolls horizontally as the plane passes by:

See the Pen deconstructing amCharts movie, city passing by by amCharts team (@amcharts) on CodePen.

This uses the same chart as the plane trail. We basically add another value axis to the chart and create a column series:

// Add a new axis to the chart var valueAxis = chart.yAxes.push(new am4charts.ValueAxis()); // ... shortened for brevity // Configure the column series var series = chart.series.push(new am4charts.ColumnSeries()) series.dataFields.dateX = "date"; series.dataFields.valueY = "value"; series.sequencedInterpolation = true; series.fillOpacity = 1; series.fill = am4core.color("#222a3f"); series.stroke = am4core.color("#222a3f") // Establish the columns at full width series.columns.template.width = am4core.percent(100);

Then we update the background to gradient:

chart.background.fillOpacity = 0.2; var gradient = new am4core.LinearGradient(); gradient.addColor(am4core.color("#222a3f")); gradient.addColor(am4core.color("#0975da")); gradient.rotation = -90; chart.background.fill = gradient;

And finally, we zoom in on the chart to half of the total range so that we can slowly change the zoom level later to create the moving city effect.

function startZoomAnimation(){ // Animate the start and end values slowly to make it look like the city is moving var animation = dateAxis.animate([{property:"start", from:0, to:0.5}, {property:"end", from:0.5, to:1}], 15000, am4core.ease.linear); animation.events.on("animationended", startZoomAnimation); }

Here is all the code, without the trail part for brevity:

am4core.useTheme(am4themes_amchartsdark); am4core.useTheme(am4themes_animated); // Main container var mainContainer = am4core.create("introchart", am4core.Container); mainContainer.width = am4core.percent(100); mainContainer.height = am4core.percent(100); var chart = mainContainer.createChild(am4charts.XYChart); chart.padding(0, 0, 0, 0) chart.zIndex = 20; var data = []; var date = new Date(2000, 0, 1, 0, 0, 0, 0); for (var i = 0; i < 40; i++) { var newDate = new Date(date.getTime()); newDate.setDate(i + 1); var value = Math.abs(Math.round(((Math.random() * 100 - i + 10) / 10)) * 10) data.push({ date: newDate, value: value }); } chart.data = data; chart.zoomOutButton.disabled = true; chart.seriesContainer.zIndex = -1; chart.background.fillOpacity = 0.2; var gradient = new am4core.LinearGradient(); gradient.addColor(am4core.color("#222a3f")); gradient.addColor(am4core.color("#0975da")); gradient.rotation = -90; chart.background.fill = gradient; var dateAxis = chart.xAxes.push(new am4charts.DateAxis()); dateAxis.renderer.grid.template.location = 0; dateAxis.renderer.ticks.template.disabled = true; dateAxis.renderer.axisFills.template.disabled = true; dateAxis.renderer.labels.template.disabled = true; dateAxis.rangeChangeEasing = am4core.ease.sinIn; dateAxis.renderer.inside = true; dateAxis.startLocation = 0.5; dateAxis.endLocation = 0.5; dateAxis.renderer.baseGrid.disabled = true; dateAxis.tooltip.disabled = true; dateAxis.renderer.line.disabled = true; dateAxis.renderer.grid.template.strokeOpacity = 0.07; var valueAxis = chart.yAxes.push(new am4charts.ValueAxis()); valueAxis.tooltip.disabled = true; valueAxis.renderer.ticks.template.disabled = true; valueAxis.renderer.axisFills.template.disabled = true; valueAxis.renderer.labels.template.disabled = true; valueAxis.renderer.inside = true; valueAxis.min = 0; valueAxis.max = 100; valueAxis.strictMinMax = true; valueAxis.tooltip.disabled = true; valueAxis.renderer.line.disabled = true; valueAxis.renderer.baseGrid.disabled = true; valueAxis.renderer.grid.template.strokeOpacity = 0.07; var series = chart.series.push(new am4charts.ColumnSeries()) series.dataFields.dateX = "date"; series.dataFields.valueY = "value"; series.sequencedInterpolation = true; series.fillOpacity = 1; series.fill = am4core.color("#222a3f"); series.stroke = am4core.color("#222a3f") series.columns.template.width = am4core.percent(100); chart.events.on("ready", startZoomAnimation); function startZoomAnimation(){ // Animate the start and end values slowly to make it look like city is moving var animation = dateAxis.animate([{property:"start", from:0, to:0.5}, {property:"end", from:0.5, to:1}], 15000, am4core.ease.linear); animation.events.on("animationended", startZoomAnimation); } The column chart appears and bends to a radar column chart

Can you guess what happens in the final scene? The initial chart (which looks like a regular column chart) is actually what’s called a radar chart with a very small arc between the chart’s startAngle (269.9°) and endAngle (270.1°) properties.

var radarChart = mainContainer.createChild(am4charts.RadarChart); // ... Chart properties go here radarChart.startAngle = 269.9; radarChart.endAngle = 270.1;

The total arc angle is only 0.2° degrees and that's why the radius of a chart becomes very big and tough tell it apart from a regular XY chart. And all we do to bend the chart is animate the start and end angles. I told you… we can literally animate everything, including angles!

radarChart.events.on("ready", bend); function bend() { var animation = radarChart.animate([{ property: "startAngle", to: 90 }, { property: "endAngle", to: 450 }], 3500, am4core.ease.cubicIn).delay(1000); animation.events.on("animationended", unbend); } function unbend() { var animation = radarChart.animate([{ property: "startAngle", to: 269.9 }, { property: "endAngle", to: 270.1 }], 3500, am4core.ease.cubicIn).delay(500); animation.events.on("animationended", bend); }

Here’s that bending animation in all its glory:

See the Pen deconstructing amCharts movie, bending the chart by amCharts team (@amcharts) on CodePen.

OK, we are done!

Oh, but there is one last, important thing I would like to mention: Containers. All charts and other non-chart objects in this movie are contained in a single div element. Initially, we created mainConatainer and arranged all the objects inside it. Containers support horizontal, vertical, grid and absolute layouts. Fixed or absolute positions can be used for a container's children, and containers can be nested inside other containers. They can be aligned horizontally or vertically, set to a fixed or absolute width or height… and so on. And did I mentioned, that amCharts has built-in date, number and text formatters? Seriously, I will stop here.

As a part of the amCharts team, I often hear comments like, "but you can do all of this with d3.” Yes, you are probably right. But there are still real benefits we’re working with here — fewer lines of code, time spent writing it, and a relatively simple startup. All the animation is made up of 1,000 lines of code, so this is also a pretty lightweight resource.

But, I’m really interested in what you think. Have you tried amCharts before and have examples to show off? How about questions about getting started? Or maybe you’re not sold on the concept altogether and want to chat the pros and cons? Let’s hear it in the comments!

The post Making Movies With amCharts appeared first on CSS-Tricks.

Escaping Gridlock: When And How To Break A Grid Layout

Usability Geek - Tue, 01/15/2019 - 1:02pm
Barring a freshly cleaned whiteboard or the latest version of Sketch, one of the most invaluable tools in a user experience designer’s toolbox is the grid layout. Ancient as design itself, grid...
Categories: Web Standards

How Well Do You Know CSS Layout?

Css Tricks - Tue, 01/15/2019 - 4:54am

The difference between a CSS good experience and a long frustrating one is oftentimes a matter of a few small details. CSS is indeed nuanced. One of the most common areas where I see struggles is layout. Personally, I like to study patterns. I notice that I tend to use a small group of patterns to solve the majority of my layout problems. This article is about those CSS patterns I use to get myself through layout challenges. It is also about approaching situations agnostically, regardless of the CSS methodologies used, whether that’s SMACSS, BEM, or even the hot topic of CSS-in-JS because they all focus on the properties themselves rather than architecture, organization, or strategy.

Just for fun, let’s start with a test

We’ll use a platform that I happen to have made called Questionable.io and I’ve used it to create a test that we’ll get to below. Don’t worry, there is no personal data collected, results are anonymous and it’s totally free.

The purpose of the test is to see if you can recognize specific CSS behaviors and problems in context without first being presented with the material. I didn’t set out to make the test difficult, but CSS layout nuances tend to be somewhat complex, especially without having a lot of exposure to them. Remember, this all for fun. The results are not an indication of your awesomeness, but hopefully you get value out of it.

The test is 10 questions and should take 10 minutes or less.

Take CSS Layout Quiz

Interested in the test but don’t want to take it? Here’s a link to the questions with their correct answers.

Done already? Great! Let’s go over the questions one-by-one to get a better understanding of the layout patterns that are covered in the test.

Question 1: Box Model

Learning the Box Model should be high priority on anyone’s list. While this CSS-Tricks Box Model Article may be a bit old, don’t underestimate its value and relevance to modern CSS. The Box Model is prerequisite knowledge for almost every CSS topic related to layout.

This particular question is testing how to get the Box Model’s computed width. The box clearly has width: 100px; but it turns out that the default rules of the Box Model apply width properties to the content layer of the box. The computed width (how wide is rendered on the page) is the sum of the content layer, padding layer, and border layer. For this reason, the answer is 112px:

.box { width: 100px; /* Take this */ height: 50px; padding: 5px; /* Plus this x2 for left and right */ border: 1px solid red; /* Plus this x2 for left and right */ background-color: red; /* = 112px of computed width */ }

If you’ve encountered a situation where the last column or tab in a UI wraps down to the next line and you were confident that five tabs (all set to width: 20%;) adds up to 100%, then it’s very possible that this was the issue. Five tabs at 20% width does add up to 100%, but if there’s padding and/or borders involved, those will add width there won’t be room for the last tab to fit on the same line.

Around the time of CSS3 being introduced, a new tool called box-sizing came to CSS. This allows us to change what layer of the Box Model we want width to apply. For example, we can do box-sizing: border-box; which means we want any width rules to apply to the outside of the border layer instead of the content layer. In this test question, if box-sizing: border-box; had been applied, the computed width would have been 100px.

This is old news for some of you but a good reminder for pros and novices alike.

There are a number of articles on the Box Model and how to use box-sizing as a reset, so it’s applied to your entire project all at once. Box Sizing and Inheriting box-sizing Probably Slightly Better Best-Practice are two great articles right on CSS-Tricks to get started.

Question 2: Borders are pushy

The second test question could almost be considered "Part Two" of the first question. Remember, it’s one thing to read, "The Box Model has layers and they all contribute to the calculated width and hight." It’s another to be able to recognize a Box Model problem in a real situation. This particular problem is somewhat of a classic among those who have been doing CSS for a while. It stems from the fact that borders take up space and will push things around since they are a part of the Box Model. Introducing borders during a state-transition, like :hover, will mean that boxes get bigger and thus push subsequent boxes down. It can also create a jittery experience:

See the Pen CSS-Tricks: Borders are Dimension by Brad Westfall (@bradwestfall) on CodePen.

Out of all the possible solutions in the test question, doing border: 2px solid transparent on the initial "un-hovered" state would be the only one that fixes the problem. box-sizing doesn’t fix this problem because we are not explicitly setting a height. If we had, then the border would be factored on the inside of the height and there would be no shift — but this wasn’t the case.

There are also other solutions that weren’t mentioned as possible answers. One is faux borders with box-shadow and the other is to use outline instead of border. Either of those would have resulting in no shifting during state changes because they are not layers in the Box Model. Here’s another CSS-Tricks article to read more about these solutions

Keep in mind that outline does not support border-radius.

Question 3: Absolute position vs. fixed position

Aside from knowing when to use each and how they differ in visual behavior, it’s also very important to know the rules for how each positioning method attaches to a parent element with its top, right, bottom, or left properties.

First, let’s review Containing Block. The short definition is that a Containing Block is most often the parent of any given element. However, the rules for Containing Block are different between absolute and fixed elements:

1. For absolute elements: The Containing Block is the nearest ancestor parent that is not static. For example, when an element is absolute-positioned, and contains top, right, bottom, or left properties, it will position relative to any parent that has a position of absolute, relative, fixed, or sticky.
2. For fixed elements: The Containing Block is the viewport, regardless of any parents that have position values other than static. Also, the scrolling behavior is different than absolute in that position: fixed; elements stay "fixed" to the viewport as it scrolls, hence the name.

Many developers believe absolute-positioned elements only seek the nearest position: relative; parent. This is a common misconception simply because position: relative is most often paired with position: absolute; to make a Containing Block. The reason it’s commonly used is because relative keeps the parent in flow which is often the desirable behavior. There are times though that the Containing Block of an absolute positioned element is also absolute positioned. This is totally okay depending on the situation. If all parents are static, then the absolute positioned element will attach to the viewport — but in a way that scrolls with the viewport:

See the Pen CSS-Tricks: Position Absolute Scrolling by Brad Westfall (@bradwestfall) on CodePen.

There is a lesser-known caveat to the two rules above: Anytime a parent has a transform property (among a few others) with a value other than none, then that parent will become the Containing Block for absolute- and fixed-positioned elements. This can be observed in this Pen where the notice is position: fixed; and the parent has transform but only when hovered:

See the Pen CSS-Tricks: Containing Blocks by Brad Westfall (@bradwestfall) on CodePen.

Question 4: Parent and first/last child collapsing margins

This is one of those CSS details that can really bite you if you don’t know how it works. There is a CSS concept called Collapsing Margins and many people are familiar with the form of it called Adjacent Siblings Collapsing Margins. However, there is another form of it called Parent and First/Last Child Collapsing Margins which is lesser known. Here is a demo of both:

See the Pen CSS-Tricks: Collapsing Margins by Brad Westfall (@bradwestfall) on CodePen.

Each paragraph tag has a top and bottom margin of 1em that are provided by the browser. So far, that’s the easy part. But why is the gap between the paragraphs not 2em (the sum of the top and bottom)? This is called Adjacent Sibling Collapsing Margins. The margins overlap such that the larger of the two margins will be the total gap size, thus the gap in this case is 1em.

There’s something else happening that’s a little strange though. Did you notice that the top margin of the first paragraph doesn’t create a gap between it and the blue container div? Instead of a gap, it’s almost like it "contributes" the margin to the parent div as if the div had the top margin. This is called Parent and First/Last Child Collapsing Margins. This form of Collapsing Margins will not happen in some circumstances if the parent has any of these:

  • Top/Bottom padding of any value bigger than 0.
  • Top/Bottom border of any width bigger than 0.
  • Block Formatting Context, which can be created by things like overflow: hidden; and overflow: auto;).
  • display: flow-root (not well supported).

When I have the pleasure of explaining this small CSS detail to people and solving it with padding or border, the response is almost always, "what about padding or border of 0?" Well, that doesn’t work because the value must be a positive integer.

In the previous example, just 1px of padding allows us to toggle between using and preventing Parent/Child Collapsing Margins. The gap that shows up between the first/last paragraphs and the parent is the 1px of padding but now the margin is being factored to the inside of the container since the padding layer creates a barrier preventing collapsing margins.

Regarding the question, I’m confident you can see what the problem is in this UI:

See the Pen CSS-Tricks: Parent/Child Collapsing Margins by Brad Westfall (@bradwestfall) on CodePen.

The first .comment (without the .moderator class) is experiencing Collapsing Margins. Even without looking at the code, we can see that the moderator comment has a border and the non-moderator one does not. In the question, there were actually three answers that were considered correct. Each one is actually already applied in the source of the Pen, they're just commented out.

One reason why this form of Collapsing Margins isn’t as widely known as the others is the wide array of ways we can "accidentally" avoid it. Flexbox and grid items create a Block Formatting Context, so we don’t see this form of Collapsing Margins there. If our "comments" UI were a real project, chances are we would have had padding on all four coordinates to create spacing all the way around, which would fix any Collapsing Margins for us. As rare as it might be, I wouldn’t want you to spend a whole day scratching you head on this one, so it’s good to keep in your thoughts when working with layout.

Here are some CSS-Tricks articles on this subject:

Question 5: Percent of what?

When it comes to using percentage units, the percent is said to be based on the Containing Block’s width or height (usually related to the parent). As we stated earlier, an element with transform will become a Containing Block, so when an element is using transform, the percentage units (for transform only) are based on its own size rather than the parent.

In this example, we can see that 50% means two different things depending on context. The first red block has margin-left: 50%; and the second red block is using transform: translateX(50%);:

See the Pen CSS-Tricks - Percentage and Transform by Brad Westfall (@bradwestfall) on CodePen.

Question 6: The Box Model strikes again... what a hangover!

Just when you thought we were done talking about Box Model...

See the Pen CSS-Tricks: Left: 0 Right: 0 by Brad Westfall (@bradwestfall) on CodePen.

The hangover stems from the fact that we are using width: 100%; on the footer and also adding padding. The container is 500px wide which means the footer's content layer (being 100%) is 500px wide before padding is applied to the outside of that layer.

The hangover can be fixed with one of these two common techniques:

  1. Use box-sizing on the footer directly or via a reset, like we discussed earlier.
  2. Remove the width and do left: 0; right: 0; instead. This is a great use case for doing a left value and a right value at the same time. Doing so will avoid Box Model issues because the width will use its default value auto to take up any available space between paddings and borders when left: 0; right: 0; are set.

One of the options was "Remove the padding on the footer." This would technically work to fix the hangover because the content layer being 100% would have no padding or border to expand it beyond the width of the container. But I think this solution is the wrong approach because we shouldn’t have to change our UI to accommodate Box Model issues that are easily avoided.

The reality for me is that I always have box-sizing: border-box; as apart of my reset. If you also do this, then perhaps you don’t see this problem often. But I still like to do the left: 0; right: 0; trick anyways because, over time, it has been more stable (at least in my experience) than having to deal with Box Model issues arising from width: 100%; on positioned elements.

Question 7: Centering absolute and fixed elements

Now we’re really starting to combine all the material from above with the centering of absolute and fixed elements:

See the Pen CSS-Tricks: Modal (Lightbox) Centering by Brad Westfall (@bradwestfall) on CodePen.

Since we’ve already covered most of the material in this test question, I’ll simply point out that horizontal and vertical centering can be done "the old school way" with negative margins or the newer "kinda old school but still good" way of doing transforms. Here is an amazing CSS-Tricks guide on all things centering.

It used to be said that if we know the width and height of the box, then we should use negative margins because they’re more stable than transitions, which were new to browsers. Now that transitions are stable, I use them almost all the time for this, unless I need to avoid a Containing Block.

Also know that we can’t use any margin: auto; tricks for this because we need modals to "hover" over the content which is why position is typically used to them out of Normal Flow.

Speaking of which, let’s move on to the next question, which deals with centering with Normal Flow.

Question 8: Centering elements with Normal Flow

Flexbox brought us many amazing tools for solving difficult layout problems. Before it’s release, it was said that vertical centering was one of the most difficult things to do in CSS. Now it’s somewhat trivial:

.parent { display: flex; } .child { margin: auto; }

See the Pen CSS-Tricks: Flexbox Centering (Vertical and Horizontal) by Brad Westfall (@bradwestfall) on CodePen.

Notice that with flexbox items, the margin: auto is being applied to top, right, bottom, and left to center vertically and horizontally. Doing vertical centering with auto didn't work in the past with block-level elements which is why doing margin: 0 auto is common.

Question 9: Calculate mixed units

Using calc() is perfect when two units that we can’t add up on our own need to be mixed or when we need to make fractions easier to read. This test question asks us to figure out what calc(100% + 1em) would be based on the fact that the width of the div is 100px. This was a little tricky because it actually doesn't matter that the div is 100px wide. The percent is based on the parent's width so the answer is Whatever 100% of the containing block's (parent's) width is plus 1em.

There are a few key places where I see myself regularly reaching for calc(). One is anytime I want to offset something by 100% but also add a fixed amount of extra space. Dropdown menus can be a good example of this:

See the Pen CSS Tricks: Calculate Mixed Units by Brad Westfall (@bradwestfall) on CodePen.

The trick here is that we want to make a "dropdown system" where the dropdown menu can be used with different trigger sizes (in this case, two different size buttons). We don’t know what the height of the trigger will be but we do know that top: 100%; will placed at the top of our menu and at the very bottom of the trigger. If every menu needs to be at the bottom of their respective trigger, plus .5em, then that can happen with top: calc(100% + 0.5em);. Sure, we could use top: 110%; as well, but that extra 10% would be context-dependent based on the height of the trigger and the container.

Question 10: Negative margins

Unlike positive margins that push away from their siblings, negative margins pull them closer together without moving the sibling elements. This final test question offers two solutions that technically work to eliminate the double border in our button group, but I strongly prefer the negative margins technique because removing borders would make it much more challenging to do certain tricks like this hover effect:

See the Pen CSS Tricks: Negative Margins with Button Groups by Brad Westfall (@bradwestfall) on CodePen.

The effect is a "common border" that is shown between the buttons. Buttons can't actually share a common border so we need this negative margin trick to make the two borders overlap. Then I'm using a z-index to manage which border I want to be on top depending on the hover state. Note that z-index is useful here even without absolute positioning, but I did have to do position: relative. If I had used the technique to remove the left border of the second button, this effect would have been more difficult to pull off.

It all adds up!

There is one last demo I want to show you that utilizes many tricks we’ve discussed so far. The task is to create UI tiles that expand all the way to the left and right edges of the container with gutters. By tiles, I mean the ability to have a list of blocks that wraps down to the next line when there’s no more space. Hover over the tiles to see the full effect:

See the Pen CSS-Tricks: Flexbox Tiles (Edge-to-Edge) by Brad Westfall (@bradwestfall) on CodePen.

The hurdle with this task is the gutters. Without gutters, it would be trivial to get the tiles to touch the left and right edge of the container. The problem is that the gutters will be created by margin, and when we add margin to all sides of the tile, we create two problems:

  1. Having three tiles with width: 33.33%; in combination with margin will mean three tiles cannot fit on one row. While box-sizing will allow us to have padding and borders on the .tile which will be contained within 33.33%, it will not help us with margins — that means the computed width of the three tiles will be more than 100%, forcing the last one down to the next line.
  2. Tiles on the far left and right side will no longer touch the edges of the container.

The first problem can be solved with calc((100% / 3) - 1em). That’s 33.33% minus the left and right margins of each tile. Adjacent Sibling Collapsing Margins don’t apply here because there is no such thing as Collapsing Margins when it comes to left and right margin. As a result, the horizontal distance between each tile is the sum of the two margins (1em). It also doesn’t apply in this case with the top and bottom margins because the first tile and the fourth tile are technically not Adjacent Siblings, even though they happen to be right next to each other visually.

With the calc() trick, three tiles are able to fit on a row, but they still don’t extend to edges of the container. For that, we can use negative margins in the amount equal to the left and right margin of each tile. The green dotted line in the example is the container where we will apply negative margins to draw out the tiles to match the edge of the surrounding content. We can see that how it extends into its parent’s padding area (the main element). That’s okay because negative margins don’t push neighboring elements around.

The end result is that the tiles have nice gutters that extend edge-to-edge so that they align to the neighboring paragraph tags outside the tiles.

There’s a lot of ways to solve tiles (and they usually come with their own pros and cons). For example, there's a rather elegant solution using CSS Grid discussed by Heydon Pickering which is responsive using a technique that mimics container queries (but with Grid magic). Ultimately, his Grid solution to tiles is much nicer than the flexbox solution I presented, but it also has less browser support. Nonetheless, the flexbox solution is still a great way to demo all the tricks from this article at the same time.

You may already be familiar with Heydon's work. He's known for creating clever tricks like the Lobotomized Owl selector. If you're not familiar, it's certainly worth knowing and I have a video where I talk about it.

Summary

I stated at the start that I tend to look for patterns when solving problems. This article isn’t necessarily about the exact demo scenarios from above; it’s more about a set of tools that can be used to solve these and many other layout problems that we are all likely to come across. I hope these tools take you far and I look forward to hearing your contributions in the comments.

By the way, there are a number of excellent resources that cover the Box Model in thorough detail, most notably ones by Rachel Andrews and Jen Simmons that are certainly worth checking out. Rachel even has a newsletter completely dedicated to layout.

  • Box Alignment Cheatsheet - Great resource with visuals that highlight the various properties that affect how elements are aligned, either by themselves or relative to other elements.
  • Jen Simmons Labs - A slew of helpful posts, demos and experiments using modern layout methods.

The post How Well Do You Know CSS Layout? appeared first on CSS-Tricks.

CSS doesn’t suck

Css Tricks - Tue, 01/15/2019 - 4:50am

I'm not so protective of CSS that I'm above hearing it criticized, but I'm certainly in agreement here. CSS does not suck. I love how the post is framed to hype up current CSS features the way features of other languages and tools are hyped:

Imagine if a tech dude walked on stage at a conference and said the following:

“This declarative language will gracefully continue on failure, allow you to write global and scoped code, and it will work across your entire front-end stack, whether it’s rendered by a framework, a CMS or a static HTML file”

... Now, if I make a slight amendment to that, the reception would probably be the exact opposite.

“CSS will gracefully continue on failure, allow you to write global and scoped code, and it will work across your entire front-end stack, whether it’s rendered by a framework, a CMS or a static HTML file”

Direct Link to ArticlePermalink

The post CSS doesn’t suck appeared first on CSS-Tricks.

In Defense of Utility-First CSS

Css Tricks - Tue, 01/15/2019 - 4:48am

A rather full-throated argument (or rather, response to arguments against) utility (atomic) CSS from Sarah Dayan. I wondered recently if redesigns were potentially a weakness of these types of systems (an awful lot of tearing down classes) which Sarah acknowledges and recommends more abstraction to help.

I also wonder about workflow. I sort of demand working in an environment which offers style injection, so working with CSS feels smooth. I also worry that having to change HTML every time I want to modify a design requires refreshing. I guess if you are in a hot module reloading situation, then it's fine.

Also this seems like it can just go too far. At some point, altering a space-separated string to do everything you wanna do has ergonomic limitations.

Direct Link to ArticlePermalink

The post In Defense of Utility-First CSS appeared first on CSS-Tricks.

Using React Portals to Render Children Outside the DOM Hierarchy

Css Tricks - Mon, 01/14/2019 - 1:22pm

Say we need to render a child element into a React application. Easy right? That child is mounted to the nearest DOM element and rendered inside of it as a result.

render() { return ( <div> // Child to render inside of the div </div> ); }

But! What if we want to render that child outside of the div somewhere else? That could be tricky because it breaks the convention that a component needs to render as a new element and follow a parent-child hierarchy. The parent wants to go where its child goes.

That’s where React Portals come in. They provide a way to render elements outside the DOM hierarchy so that elements are a little more portable. It may not be a perfect analogy, but Portals are sort of like the pipes in Mario Bros. that transport you from the normal flow of the game and into a different region.

The cool thing about Portals? Even though they trigger their own events that are independent of the child’s parent element, the parent is still listening to those events, which can be useful for passing events across an app.

We’re going to create a Portal together in this post then make it into a re-usable component. Let’s go!

The example we’re building

Here’s a relatively simple example of a Portal in action:

See the Pen React Portal by Kingsley Silas Chijioke (@kinsomicrote) on CodePen.

Toggling an element’s visibility is nothing new. But, if you look at the code carefully, you’ll notice that the outputted element is controlled by the button even though it is not a direct descendent of it. In fact, if you compare the source code to the rendered output in DevTools, you’ll see the relationship:

So the outputted element’s parent actually listens for the button click event and allows the child to be inserted even though it and the button are separate siblings in the DOM. Let’s break down the steps for creating this toggled Portal element to see how it all works.

Step 1: Create the Portal element

The first line of a React application will tell you that an App element is rendered on the document root using ReactDOM. Like this;

ReactDOM.render(<App />, document.getElementById("root"));

We need to place the App element in an HTML file to execute it:

<div id="App"></div>

Same sort of thing with Portals. First thing to creating a Portal is to create a new div element in the HTML file.

<div id="portal"></div>

This div will serve as our target. We’re using #portal as the ID, but it doesn’t have to be that. Any component that gets rendered inside this target div will maintain React’s context. We need to store the div as the value of a variable so we can make use of the Portal component that we’ll create:

const portalRoot = document.getElementById("portal");

Looks a lot like the method to execute the App element, right?

Step 2: Create a Portal component

Next, let’s set up the Portal as a component:

class Portal extends React.Component { constructor() { super(); // 1: Create a new div that wraps the component this.el = document.createElement("div"); } // 2: Append the element to the DOM when it mounts componentDidMount = () => { portalRoot.appendChild(this.el); }; // 3: Remove the element when it unmounts componentWillUnmount = () => { portalRoot.removeChild(this.el); }; render() { // 4: Render the element's children in a Portal const { children } = this.props; return ReactDOM.createPortal(children, this.el); } }

Let’s step back and take a look at what is happening here.

We create a new div element in the constructor and set it as a value to this.el. When the Portal component mounts, this.el is appended as a child to that div in the HTML file where we added it. That’s the <div id="portal"></div> line in our case.

The DOM tree will look like this.

<div> // Portal, which is also portalRoot <div> // this.el </div> </div>

If you’re new to React and are confused by the concept of mounting and unmounting an element, Jake Trent has a good explanation. TL;DR: Mounting is the moment the element is inserted into the DOM.

When the component unmounts we want to remove the child to avoid any memory leakage. We will import this Portal component into another component where it gets used, which is the the div that contains the header and button in our example. In doing so, we’ll pass the children elements of the Portal component along with it. This is why we have this.props.children.

Step 3: Using the Portal

To render the Portal component’s children, we make use of ReactDOM.createPortal(). This is a special ReactDOM method that accepts the children and the element we created. To see how the Portal works, let’s make use of it in our App component.

But, before we do that, let’s cover the basics of how we want the App to function. When the App loads, we want to display a text and a button — we can then toggle the button to either show or hide the Portal component.

class App extends React.Component { // The initial toggle state is false so the Portal element is out of view state = { on: false }; toggle = () => { // Create a new "on" state to mount the Portal component via the button this.setState({ on: !this.state.on }); }; // Now, let's render the components render() { const { on } = this.state; return ( // The div where that uses the Portal component child <div> <header> <h1>Welcome to React</h1> </header> <React.Fragment> // The button that toggles the Portal component state // The Portal parent is listening for the event <button onClick={this.toggle}>Toggle Portal</button> // Mount or unmount the Portal on button click <Portal> { on ? <h1>This is a portal!</h1> : null } </Portal> </React.Fragment> </div> ); } }

Since we want to toggle the Portal on and off, we need to make use of component state to manage the toggling. That’s basically a method to set a state of on to either true or false on the click event. The portal gets rendered when on is true; else we render nothing.

This is how the DOM looks like when the on state is set to true.

When on is false, the Portal component is not being rendered in the root, so the DOM looks like this.

More use cases

Modals are a perfect candidate for Portals. In fact, the React docs use it as the primary example for how Portals work:

See the Pen Example: Portals by Dan Abramov (@gaearon) on CodePen.

It’s the same concept, where a Portal component is created and a state is used to append the its child elements to the Modal component.

We can even insert data from an outside source into a modal. In this example, the App component lists users fetched from an API using axios.

See the Pen React Portal 3 by Kingsley Silas Chijioke (@kinsomicrote) on CodePen.

How about tooltips? David Gilberston has a nice demo:

See the Pen React Portal Tooptip by David Gilbertson (@davidgilbertson) on CodePen.

J Scott Smith shows how Portals can be used to escape positioning:

He has another slick example that demonstrates inserting elements and managing state:

Summary

That’s a wrap! Hopefully this gives you a solid base understanding of Portals as far as what they are, what they do, and how to use them in a React application. The concept may seem trivial, but having the ability to move elements outside of the DOM hierarchy is a handy way to make components a little more extensible and re-usable… all of which points to the core benefits of using React in the first place.

More information

The post Using React Portals to Render Children Outside the DOM Hierarchy appeared first on CSS-Tricks.

Design v17

Css Tricks - Mon, 01/14/2019 - 4:51am

We rolled out a new site design on January 1! This is the 17th version of CSS-Tricks if you can believe that. The versions tend to evolve a decent amount beyond the initial launch, but we archive screenshots on this design history page. Like I said in our 2018 thank you post:

This is easily the most time, effort, and money that's gone into a redesign since the big v10 design. There are a lot of aesthetic changes, but there was also quite a bit of UX work, business goal orientation, workflow tweaking, and backend development work that went along with it.

This is a big one! The reception so far has been pretty great, but please know that we'll be refining it and squishing a lot of bugs here in the early days.

Here are some notes about who was involved, how it happened, and things to notice.

Kylie led this project

Kylie Timpani was the lead designer and really whole project lead on this.

I first reached out to her in April 2017, we chatted in May, and kicked off the work in June. From my perspective, this was a pretty casual process, as I had no particular deadlines and fairly loose goals. Let's make an attractive site that does better in all ways than we do them now.

Kylie was super organized and had a very thoughtful process around every aspect of this. Just in the first block of time that Kylie allocated for this project she:

  • Took a complete content inventory
  • Dug into analytic data to understand users, traffic, and usage at a high level
  • Created, distributed, and analyzed a reader survey to understand readers better and answer specific questions she had for them
  • Chatted with all the staff members of CSS-Tricks to understand their roles, workflows, and ideas

Kylie's obviously not the kind of the designer that just whips open a design tool and starts noodling around. As great of a visual designer as she is, the work was highly informed. She went on to speak with our advertising agency, clearly identify the site's current strengths and weaknesses, and do light wireframing.

I've been using Figma for visual design stuff, and Kylie was happy to use that as the design tool. That was nice since we both have Team level access and were able to use it collaboratively. For me, it was mostly useful for being able to see and reference everything, and make notes on the designs.

We also used Asana to track what was being worked on and ultimately as a place to track bugs and places where the design implementation needed attention.

Thanks so much, Kylie for all your excellent work on this project! If anything is a bit off or buggy about the site, it's my poor implementation. And good luck! ⤵️

⚡️ So! Speaking of big life changing news... in just a couple of weeks I'll be moving to San Francisco! I'm so SO excited to be joining @nrrrdcore and her truly incredible team over at Apple! &#x1f469;&#x1f3fb;‍&#x1f4bb;✨

— Kylie Timpani (@kylietimpani) January 7, 2019

Design Stuff

I'll let y'all explore the design for yourself to find all the little touches we put in, but I'll give a shout out to a few of them where there is a technical detail you might enjoy.

Orange-to-pink

Clearly, we went all dark mode on this design. It's nothing to do with the new media query, although that reminds me we might consider alternations for those who specifically set prefers-color-scheme: light;.

The brand/accent/action colors are orange and pink, which looks quite striking against the darkness but works on light backgrounds as well.

I made a quickie little Sass @mixin that allowed me to use those colors (with variations, if needed) at different angles as backgrounds:

@mixin orange-to-pink($deg: to right, $col-1: $orange, $col-2: $pink) { background-image: linear-gradient($deg, $col-1, $col-2); }

And on text as well wherever we needed (most often as for :hover and :focus):

@mixin gradient-text() { background: linear-gradient(to right, $orange, $pink); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }

We also needed the gradient to be applied to fills and strokes in SVG, so I plopped that into the document to use wherever it was needed.

<svg width="0" height="0" class="visually-hidden"> <defs> <linearGradient id="orange-to-pink" x1="0" x2="0" y1="1" y2="0"> <stop offset="0%" stop-color="#DA1B60" /> <stop offset="100%" stop-color="#ff8a00" /> </linearGradient> </defs> </svg> Fixed header

There is something about headers that always bring out more complexity than you might expect. I recently went through this with the new CodePen header/sidebar and it as complicated for this site. Part of what complicated this one was:

  • It has its own set of unique breakpoints. The header is pretty full, so the breakpoints are pretty specific and unique to it.
  • We wanted a fixed-position (but minified) header that showed up as you scroll down.
  • When you're logged in, there is a WordPress admin bar also fixed to the top of the page. I wanted to accommodate for that.

At one point, it was getting pretty messy and I wound up deleting all the CSS for the entire thing and re-wrote it, taking all the states into consideration, and writing media queries that used logic to clearly specify styles in each of those states.

The idea of a not-always-fixed-position header is interesting in and of itself. It means that:

  • You need to determine when to apply the fixed position
  • You need to make sure the shift from not-fixed to fixed (and back) doesn't cause layout shifting

I was dead nervous about attaching an onscroll listener and doing math and such to determine when to do the switch. I'm sure it can be done responsibly, but I haven't had great luck with that. Instead, I placed a tiny one-pixel element to the screen and attached an IntersectionObserver to it and reacted to that. That gave me the power to adjust where that is in CSS, which was a nice little touch.

Here's the very basics of that code:

See the Pen Fixed Header with IntersectionObserver by Chris Coyier (@chriscoyier) on CodePen.

Mixup

One very cool feature of this design is the Mixup area on the homepage. It was one of Kylie's ideas to show and remind people of the variety and depth of content that is here on CSS-Tricks.

The line that goes through it needs to depend on the height of the HTML content in each of those boxes. The boxes are set on a CSS grid, but they can and should still expand as needed for titles and such. Rather than try to SVG this somehow, the line is essentially stitched together though border and border-radius on individual boxes. To make it line up, I occasionally had to nudge them around with transform.

There was some z-index involved too. It was fun making mistakes along the way:

Cards

I'm kinda in love with native scroll snapping. The cards kinda have a fun animation on desktop, revealing the entire card on hover/focus, and then on mobile you can see the whole card, but are easy to thumb through:

Thanks, Amelia!

The design called for these curved line separators:

I have a small degree of confidence with the SVG path syntax, so I took the first crack at it. I was able to design it in a way that it could draw that line OK and keep the stroke at the desired width, but it didn't scale quite right.

See the Pen Lighted Path by Chris Coyier (@chriscoyier) on CodePen.

I brought in SVG expert Amelia Bellamy-Royds to help me get it right. Feel free to inspect the site to see how it was done. It involves masking and nested SVGs and rectangles and transforms and all sorts of fun stuff. Amelia actually created four variations of the code and carefully noted all the pros and cons of each one. Ultimately, we went with this:

See the Pen Lighted Path by Amelia Bellamy-Royds (@AmeliaBR) on CodePen.

Another thing Amelia helped with was the "circle of text" design element. Kylie had these instances mocked out and I thought they were so cool and I definitely wanted to pull it off. There is a really elaborate way to do it by splitting the characters in to spans and transforming them, but that's a bit messy compared to SVG's <textPath>. I knew I wanted to go the SVG route, but perhaps abstract it away into a reusable component so that it wasn't a heaping pile of code every time I want to use one.

It occurred to me that a web component might be the best way to go here because I can kind of invent the API myself. What I wanted a circle-of-text component to do:

  1. Pass in the text to set on the circle
  2. Declare the radius of the circle
  3. Rotate the circle so I can start the text at any point along the circle

That makes perfect sense as a web component:

<circle-text r="3em" rotate="-90deg"> CSS is super fun & cool & I like CSS!!! </circle-text>

My expertise with web components is limited, so I reached out to Amelia again who is great both with web components and SVG—a perfect match! This is what she was able to do, which I easily integrated easily into this design.

Thanks, Ana!

Another design thing that Kylie cooked up that I was a bit perplexed by was this line:

I thought maybe SVG again, but I really wanted to nestle regular HTML content in there nicely. I was hoping to pull it over with borders or something CSS-y. I reached out to Ana Tudor who is fantastic at tricky design situations and solving them with native browser tech. Ana was able to whip up a good solution here using multiple gradient backgrounds in the main area and a border for the top right bit that flies off.

See the Pen nav by Ana Tudor (@thebabydino) on CodePen.

Thanks, Zach!

Fonts are a unique part of the loading experience of websites in that their presence (or lack of), how they appear, and how they change all play major roles in the perceived performance of the page.

I've had the good fortune of being able to chat with Zach Leatherman about font loading before, but I still don't feel entirely comfortable with what the best practices are in any given situation. For this design of CSS-Tricks, I made the call to use the system font stack for most of the body copy. That has the major benefit of being instantly available to render and aesthetically seems to work well on a technical site, not to mention generally pairing well with Rubik, our header font.

But we still needed to deal with Rubik. There will be an upcoming article from Zach going into this in more details, but the gist is:

  1. Create a minimal subsetted version of Rubik that handles the majority of usage
  2. <link rel="preload" ... > it
  3. Use it with @font-face using font-display
  4. Load a more robust version in an async second stage

Nice job everyone that worked on the @css relaunch!

Look at those web fonts showing up on that 2.09s Fast 3G first render &#x1f389;

(full disclosure I helped a wee bit with the font loading here &#x1f607;) pic.twitter.com/Ih7zJhelQQ

— Zach Leatherman (@zachleat) January 1, 2019

Some areas of the site are somewhat deprecated

The Forums is such a complicated area of the site to design and maintain, what I've done is just loaded the default bbPress styling for them, instead of trying to override things or start from scratch. I think that'll be the best route going forward.

There is a Gallery section of this site, but I'm not even linking to it anymore as we didn't really keep it up to date very well nor did it get used much. The URL's still work though. Maybe it can make a return someday, but for now, I'm enjoying the reduction of some technical and content debt.

Tech stack

It's somewhat boring. It's about the same thing I've done forever. It's a stock install of WordPress with a custom theme, a dozen or so plugins, and a bit of custom-coded functionality, like having the images powered by Cloudinary. It's running on a custom Media Temple-built box so it can have PHP 7 and MySQL 5.6, plus a firewall that also acts as a CDN. It's nice to have a pretty snappy foundation, so it's on me as a front-end dev to keep it that way.

I used SVG for the icons, Sass for the styling, and Babel to write jQuery-based functionality in ES6. I wrote up a Gulp file to do all that processing and run the local BrowserSync dev server. Local WordPress via Local by Flywheel.

I'm actually pretty happy with the stack as it felt quick and productive to me. But I admit, part of me wishes I dug a little harder into new tech, like building webpack-based processing or trying to go all-in on a server-rendered and React-powered headless WordPress via GraphQL kinda thing. The reason I didn't is because boring has served me so well and time is a major factor since I'm developing alone (my budget doesn't exactly make available a whole development team). My guess is a major front-end infastructure overhaul would have tripled the dev time for questionable benefits. It still sounds like fun and might open up future doors, but hey, another time.

My last regret is that I wish I had spun up a real pattern library system from the start. I think I did OK in breaking things up into reusable parts, but the site isn't truly componentized. As I approached the finish line, I started to see how this could have gone a bit smoother for me should I have worked with true components that accepted data and had variations and such. Native PHP isn't great for that, so it would have forced me into some kind of templating system, and I probably wouldn't have regretted it. If I stay in PHP next time, maybe I'd use something like Timber and Twig for all the components, and then Fractal for the pattern library since it supports Twig. I kind of dig the way Timber abstracts the data stuff from the views.

I hadn't heard of this app until now, but check out Project Wallace:

Project Wallace is a project aimed at gaining insights in your CSS over a longer period of time. It started a couple of years ago as a frustration with existing CSS analyzers that only do a one-time only analysis. As time went by, more and more features were added and now Wallace is place to go for developers who want to know if their complexicity has increased or for a designer who wants to know if all the correct colors and fonts are being used.

Bart Veneman set it up to watch CSS-Tricks, and you can see a before/after comparison and charts over time. Bart blogged about the numbers for us as well. Thanks Bart!

There are more interesting stats in that blog post. CodePen embeds

The true usefulness of CodePen Embed Themes came out here. The whole point of an embed theme is that you can use them to match the design of where the Pens will be embedded, and if you need to change that design, you can change them all in one fell swoop. There are probably thousands of embedded Pens on this site, and they all got updated at once with one theme change.

There are a few special things that I've done with CodePen embeds on this site:

  • The are resizable from the bottom right corner. Used jQuery. Like this.
  • They have a placeholder height. When you embed a Pen, you can choose how tall you want it to be. That's how tall the <iframe> will come in as. But I've adjust it so that the <p> that is there before the iframe comes in will be that same height, so there is no reflow jank.

I regex'd that sucker myself like this:

function codepen_reflow_fix($content) { $content = preg_replace('/data-height=(\'|")(\d*)(\'|")/', 'data-height="$2" style="height: $2px;"', $content); $content = preg_replace('/data-theme-id="\d*"/', 'data-theme-id="1"', $content); return $content; } add_filter('the_content', 'codepen_reflow_fix', 10);

We're gonna bring that feature to CodePen itself real soon. Notice in that RegEx above I'm also forcing the theme id. That way, all embedded Pens definitely have the correct theme, even if we forget.

Achievement unlocked: The custom scrollbar is the new feature that everyone either loves or hates

If there has been one constant in every CSS-Tricks design, it's that there's at least one feature people either love or hate. This time, I'm happy to announce it's the custom scrollbar. In a sense, it's for myself. I manually use scrollbars quite a bit and it feels both fun and highly usable to grab onto this big beefy chunk of pink love.

It's also a little inspired by VS Code, which features a pretty beefy scrollbar itself:

There are general usability considerations about custom scrollbars for sure, but I don't feel like they've been breached too heavily here, if at all. I've heard some "don't mess with my browsers UI" feedback, which I sorta get, but does that mean we shouldn't style any form controls, or even use CSS at all? (LOL.) And don't scrollbars come from the system, not the browser?

Anyway, I'm not faking them or anything. I'm just using ::-webkit-scrollbar and friends. There is official scrollbar styling stuff on the way, per the CSS specs. I didn't use any of that stuff/ I think I'll wait for at least one browser to support it.

We have plenty of bug fixing and polishing to do still on this design. If you've emailed or tweeted or communicated with us in some way about it, I've probably seen it and have been log it all to make sure it's all addressed the best we can. Plus stay tuned for some fun new features!

If you have thoughts, free to leave comments here if you like, use our contact form, email at team@css-tricks.com, or chat it up in our new Spectrum community.

The post Design v17 appeared first on CSS-Tricks.

The Ethics of Web Performance

Css Tricks - Mon, 01/14/2019 - 4:46am

Tim Kadlec on the issues surrounding poor web performance and why it’s so important for us to care about making our sites as fast as possible:

Poor performance can, and does, lead to exclusion. This point is extremely well documented by now, but warrants repeating. Sites that use an excess of resources, whether on the network or on the device, don’t just cause slow experiences, but can leave entire groups of people out.

There is a growing gap between what a high-end device can handle and what a middle to low-end device can handle. When we build sites and applications that include a lot of CPU-bound tasks (hi there JavaScript), at best, those sites and applications become painfully slow on people using those more affordable, more constrained devices. At worst, we ensure that our site will not work for them at all.

Forget about comparing this year’s device to a device a couple of years old. Exclusion can happen on devices that are brand-new as well. The web’s growth is being pushed forward primarily by low-cost, underpowered Android devices that frequently struggle with today’s web.

As Tim mentions at the end of that piece though, it’s easy to forget web performance and it’s sometimes hard to make the case for making a website fast. It’s often seen as a nice-to-have instead of as a core feature in and of itself, like semantic markup and accessibility compliance.

I’m optimistic that the conversation surrounding this topic is improving things though. Having tools like Lighthouse built straight into the browser makes things easier and the abundance of testing tools such as Calibre gives us insights into exactly what and where issues might be. But we also need to remember that this isn’t solely a technical problem — it’s an ethical one, too.

Direct Link to ArticlePermalink

The post The Ethics of Web Performance appeared first on CSS-Tricks.

Slice and Dice a Disc with CSS

Css Tricks - Sun, 01/13/2019 - 12:05pm

I recently came across an interesting sliced disc design. The disc had a diagonal gradient and was split into horizontal slices, offset a bit from left to right. Naturally, I started to think what would the most efficient way of doing it with CSS be.

Sliced gradient disc.

The first thought was that this should be doable with border-radius, right? Well, no! The thing with border-radius is that it creates an elliptical corner whose ends are tangent to the edges it joins.

My second thought was to use a circle() clipping path. Well, turns out this solution works like a charm, so let's take a close look at it!

Note that the following demos won't work in Edge as Edge doesn't yet support clip-path on HTML elements. It could all be emulated with nested elements with overflow: hidden in order to have cross-browser support, but, for simplicity, we dissect the clip-path method in this article.

Slicing a disc into equal parts

As far as the HTML structure goes, we generate it with a preprocessor to avoid repetition. First off, we decide upon a number of slices n. Then we pass this number to the CSS as a custom property --n. Finally, we generate the slices in a loop, passing the index of each to the CSS as another custom property --i.

- var n = 8; style :root { --n: #{n} } - for(var i = 0; i < n; i++) .slice(style=`--i: ${i}`)

Moving on to the CSS, we first decide upon a diameter $d for our disc. This is the width of our slices. The height is the diameter divided by the number of items calc(#{$d}/var(--n)).

In order to be able to tell them apart, we give our slices dummy backgrounds determined by parity.

$d: 20em; .slice { --parity: 0; width: $d; height: calc(#{$d}/var(--n)); background: hsl(36, calc(var(--parity)*100%), calc(80% - var(--parity)*30%)); &:nth-of-type(2n) { --parity: 1 } }

We also position our slices in the middle with a column flex layout on their container (the body in our case).

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

To get the disc shape we use a circle() clipping path having the radius $r equal to half the diameter .5*$d and the central point dead in the middle of the assembly. Since we set this clip-path on the slices, the position of the central point for each slice is relative to the slice itself.

Horizontally, it's always in the middle, at 50% of the slice. Vertically, it needs to be in the middle of the assembly, so that's where the total number of items and the item's index which we've passed as CSS variables from the preprocessor code come into play.

In the middle of the assembly means at half the height of the assembly from the top of the assembly. Half the height of the assembly is half the diameter .5*$d, which is equivalent to the radius $r. But this value is relative to the whole assembly and we need one that's relative to the current slice. In order to get this, we subtract the vertical position of the current slice relative to the assembly, that is, how far the top of the current slice is relative to the top of the assembly.

The first slice (of index --i: 0) is at the very top of the assembly, so the amount we subtract in this case is 0.

The second slice (of index --i: 1) is at one slice height from the top of the assembly (the space occupied by the first slice), so the amount we subtract in this case is 1 slice heights.

The third slice (of index --i: 2) is at two slice heights from the top of the assembly (the space occupied by the first and second slices), so the amount we subtract in this case is 2 slice heights.

In the general case, the amount we subtract for each slice is the slice's index (--i) multiplied by one slice height.

--h: calc(#{d}/var(--n)); /* slice height */ clip-path: circle($r at 50% calc(#{$r} - var(--i)*var(--h))

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

After doing this, we can offset the slices based on parity.

--sign: calc(1 - 2*var(--parity)); transform: translate(calc(var(--sign)*2%))

We now have our sliced disc!

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

Spacing out the slices

The first thought that comes to mind here is to use a margin on each slice.

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

This may be a good result in some cases, but what if we don't want our disc to get elongated?

Well, we have the option of limiting the background to the content-box and adding a vertical padding:

box-sizing: border-box; padding: .125em 0; background: hsl(36, calc(var(--parity)*100%), calc(80% - var(--parity)*30%)) content-box;

Of course, in this case, we need to make sure box-sizing is set to border-box so that the vertical padding doesn't add to the height.

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

The one little problem in this case is that it also cuts off the top of the first slice and the bottom of the last slice. This may not be an issue in some cases and we can always reset the padding-top on the :first-of-type and the padding-bottom on the :last-of-type to 0:

.slice { /* other styles */ padding: .125em 0; &:first-of-type { padding-top: 0 } &:last-of-type { padding-bottom: 0 } }

However, we also have a one-line solution to this problem of creating gaps in between the slices: add a mask on the container!

This mask is a repeating-linear-gradient() which creates transparent stripes of the thickness of the gap $g, repeats itself after a slice height and is limited to the disc diameter $d horizontally and to the disc diameter $d minus a gap $g vertically (so that we don't mask out the very top and the very bottom as we also did initially with the padding approach).

mask: repeating-linear-gradient(red 0, red calc(var(--h) - #{$g}), transparent 0, transparent var(--h)) 50% calc(50% - #{.5*$g})/ #{$d} calc(#{$d} - #{$g})

Note that in this case we need to set the slice height variable --h on the container as we're using it for the mask.

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

Continuous background

In order to have a continuous gradient background, we need to give this background a height equal to that of the disc and set its vertical position relative to each slice such that it always starts from the top of the assembly... wherever that may be located relative to the slice.

The top of the first slice (of index --i: 0) coincides with that of the assembly, so our background starts from 0 vertically.

The top of the second slice (of index --i: 1) is 1 slice height below that of the assembly, so its background starts from 1 slice height above vertically. Since the positive direction of the y axis is down, this means our background-position along the y axis is calc(-1*var(--h)) in this case.

The top of the third slice (of index --i: 2) is 2 slice heights below that of the assembly, so its background starts from 2 slice heights above vertically. This makes our background-position along the y axis is calc(-2*var(--h)).

We notice a pattern here: in general, the background-position along the y axis for a slice is calc(-1*var(--i)*var(--h)).

background: linear-gradient(#eccc05, #c26e4c, #a63959, #4e2255, #333f3d) /* background-position */ 50% calc(-1*var(--i)*var(--h))/ 100% $d /* background-size */

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

But if we want a left to right gradient, then our background isn't continuous anymore, something that becomes really obvious if we tweak the stop positions a bit in order to have abrupt changes:

background: linear-gradient(90deg, #eccc05 33%, #c26e4c 0, #a63959 67%, #4e2255 0, #333f3d) /* background-position */ 50% calc(-1*var(--i)*var(--h))/ 100% $d /* background-size */

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

In order to fix this issue, we set the offset as a Sass variable $o, set the horizontal background-size to the slice width (100% or $d) plus twice the offset and make sure we attach the background for the slices that move to the left (in the negative direction of the x axis, so by -$o) on the left side of the slice (background-position along the x axis is 0%) and for the slices that move to the right (in the positive direction of the x axis, so by $o) on the right side of the slice (background-position along the x axis is 100%).

$o: 2%; transform: translate(calc(var(--sign)*#{$o})); background: linear-gradient(90deg, #eccc05 33%, #c26e4c 0, #a63959 67%, #4e2255 0, #333f3d) /* background-position */ calc((1 - var(--parity))*100%) calc(-1*var(--i)*var(--h))/ calc(100% + #{2*$o}) $d /* background-size */

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

This works for gradients at any angle, as it can be seen in the interactive demo below - drag to change the gradient angle:

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

It also works for images, though in this case we need to remove the second background-size value so the image doesn't get distorted, which leaves us with the caveat of getting vertical repetition if the image's aspect ratio is greater than calc(#{$d} + #{2*$o}) : #{$d}. This isn't the case for the square image we're using below, but it's still something to keep in mind.

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

Another thing to note is that above, the top of the image is attached to the top of of the assembly. If we want the middle of the image to be attached to the middle of the assembly, we need to tweak the vertical component of the background-position a bit.

First off, to attach the middle of the image to the middle of a slice, we use a background-position value of 50%. But we don't want the middle of the image in the middle of each slice, we want it in the middle of the assembly for all slices. We already know the distance from the top of each slice to the vertical midpoint of the whole assembly - it's the y coordinate of the clipping circle's central point:

--y: calc(#{$r} - var(--i)*var(--h)); clip-path: circle($r at 50% var(--y))

The distance from the vertical midpoint of each slice to that of the assembly is this value --y minus half a slice's height. So it results that the background-position we need along the y axis in order to have the vertical midpoint of the image attached to that of the assembly is calc(50% + var(--y) - .5*var(--h)).

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

Incremental slices

This means our slices don't have the same height anymore. For example, the first one could have a unit height, the second one twice this height, the third one three times this height and so on...

The added heights of all these slices should equal the disc diameter. In other words, we should have the following equality:

h + 2*h + 3*h + ... + n*h = d

This can also be written as:

h*(1 + 2 + 3 + ... + n) = d

which makes it easier to notice something! Within the parenthesis, we have the sum of the first n natural numbers, which is always n*(n + 1)/2!

So our equality becomes:

h*n*(n + 1)/2 = d

This allows us to get the unit height h:

h = 2*d/n/(n + 1)

Applying this to our demo, we have:

--h: calc(#{2*$d}/var(--n)/(var(--n) + 1)); height: calc((var(--i) + 1)*var(--h));

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

Just like in the case of equal slices, the y coordinate of the central point of the clipping circle() is the disc radius $r minus the distance from the top of the assembly to the top of the current slice. This is the sum of the heights of all previous slices.

In the case of the first slice (--i: 0), we have no previous slice, so this sum is 0.

In the case of the second slice (--i: 1), we only have the first slice before and its height is the unit height (--h).

In the case of the third slice (--i: 2), the sum we want is that between the height of the first slice, which equals the unit height and that of the second slice, which is twice the unit height. That's calc(var(--h) + 2*var(--h)) or calc(var(--h)*(1 + 2)).

In the case of the third slice (--i: 3), the sum is that between the height of the first slice, which equals the unit height, that of the second slice, which is twice the unit height and that of the third slice, which is three times the unit height. That's calc(var(--h) + 2*var(--h) + 3*var(--h)) or calc(var(--h)*(1 + 2 + 3)).

Now we can see a pattern emerging! For every slice of index --i, we have that the added height of its previous slices is the unit height --h times the sum of the first --i natural numbers (and the sum of the first --i natural numbers is calc(var(--i)*(var(--i) + 1)/2)). This means our clip-path value becomes:

circle($r at 50% calc(var(--h)*var(--i)*(var(--i) + 1)/2))

We add the offset back in and we have the following result:

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

Sadly, having incremental slices means the repeating-linear-gradient() mask method of creating gaps cannot work anymore. What still works however just fine is the vertical padding method and we can set the padding values such that the top one is 0 for the first slice and the bottom one is 0 for the last slice.

padding: calc(var(--i)*#{$g}/var(--n)) /* top */ 0 /* lateral */ calc((var(--n) - 1 - var(--i))*#{$g}/var(--n)) /* bottom */

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

For a gradient background, the main idea remains the same as in the case of the equal slices. There are just two things we need to take into account.

One, the background-position along the y axis is minus the distance (in absolute value) between the top of the assembly and the top of the current slice. This distance isn't calc(var(--i)*var(--h)) like in the case of equal slices of height --h anymore. Instead it's, as computed a bit earlier, calc(var(--i)*(var(--i) + 1)/2*var(--h)). So the background-position along the y axis is calc(-1*var(--i)*(var(--i) + 1)/2*var(--h)).

And two, we want our background clipped to the content-box so that we keep the gaps, but we need to keep the background-origin to its initial value of padding-box so that our gradient stays continuous.

background: linear-gradient(var(--a), #eccc05, #c26e4c, #a63959, #4e2255, #333f3d) /* background-position */ calc((1 - var(--parity))*100%) /* x component */ calc(-1*var(--i)*(var(--i) + 1)/2*var(--h)) /* y component */ / /* background-size */ calc(100% + #{2*$o}) $d padding-box /* background-origin */ content-box /* background-clip */;

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

For an image background whose midpoint is attached to the middle of our assembly, we need to take into account the fact that half a slice height isn't the same value for all slices anymore. Now the height of a slice is calc((var(--i) + 1)*var(--h)), so this is the value we need to subtract in the formula for the y component of the background-position.

--y: calc(#{$r} - .5*var(--i)*(var(--i) + 1)*var(--h)); background: url(/amur_leopard.jpg) /* background-position */ calc((1 - var(--parity))*100%) /* x component */ calc(50% + var(--y) - .5*(var(--i) + 1)*var(--h)) /* y component */ / /* background-size */ calc(100% + #{2*$o}) padding-box /* background-origin */ content-box /* background-clip */; clip-path: circle($r at 50% var(--y));

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

Vertical slices

We can also slice our disc along the other direction. This means removing the flex-direction: column declaration from the container and letting the flex-direction be the initial one (row), switching the width and the height, the x and y coordinates of the circular clipping path's central point, the direction along which we shift the slices, the dimensions and x and y positions of the masking gradient, which we also need to rotate so that it goes along the x axis.

body { /* same as before */ --w: calc(#{$d}/var(--n)); mask: repeating-linear-gradient(90deg, red 0, red calc(var(--w) - #{$g}), transparent 0, transparent var(--w)) calc(50% - #{.5*$g}) 50% / calc(#{$d} - #{$g}) #{$d} } .slice { /* same as before */ width: var(--w); height: $d; transform: translatey(calc(var(--sign)*2%)); background: hsl(36, calc(var(--parity)*100%), calc(80% - var(--parity)*30%)); clip-path: circle($r at calc(#{$r} - var(--i)*var(--w)) 50%) }

This gives us equal vertical slices with alternating backgrounds:

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

For the gradient case, we need to also reverse the two background dimensions and the background positions along the x and y axes:

background: linear-gradient(135deg, #eccc05 15%, #c26e4c, #a63959, #4e2255, #333f3d 85%) /* background-position */ calc(-1*var(--i)*var(--w)) calc((1 - var(--parity))*100%)/ #{$d} calc(100% + #{2*$o}) /* background-size */

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

For incremental slices, we combine the incremental case with the vertical case, which means swapping the values we have for the previous incremental case along the two axes:

--w: calc(#{2*$d}/var(--n)/(var(--n) + 1)); width: calc((var(--i) + 1)*var(--w)); height: $d; clip-path: circle($r at calc(#{$r} - .5*var(--i)*(var(--i) + 1)*var(--w)) 50%);

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

To create the gaps, we use the padding method. But since we're now in the vertical case, we need horizontal paddings, on the left and on the right and to make sure the padding-left for the first slice is 0 and the padding-right for the last slice is also 0:

box-sizing: border-box; padding: 0 /* top */ calc((var(--n) - 1 - var(--i))*#{$g}/var(--n)) /* right */ 0 /* bottom */ calc(var(--i)*#{$g}/var(--n)) /* left */; background: hsl(36, calc(var(--parity)*100%), calc(80% - var(--parity)*30%)) content-box

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

Finally, we have the gradient case:

background: linear-gradient(135deg, #eccc05 15%, #c26e4c, #a63959, #4e2255, #333f3d 85%) /* background-position */ calc(-.5*var(--i)*(var(--i) + 1)*var(--w)) /* x component */ calc((1 - var(--parity))*100%) /* y component */ / /* background-size */ #{$d} calc(100% + #{2*$o}) padding-box /* background-origin */ content-box /* background-clip */;

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

2D case

Again, we generate it with a bit of Pug, the total number of items being the product between the number of columns and the number of rows. For simplicity, we keep the number of rows and the number of columns equal.

- var n = 8, m = Math.pow(n, 2); style :root { --n: #{n}; --i: 0; --j: 0 } - for(var i = 1; i < n; i++) { | .tile:nth-of-type(#{n}n + #{i + 1}) { --i: #{i} } | .tile:nth-of-type(n + #{n*i + 1}) { --j: #{i} } - } - for(var i = 0; i < m; i++) .tile

We've also passed the column and row indices (--i and --j respectively) to the CSS.

Since we're in the 2D case, we switch from using a 1D layout (flex) to using a 2D one (grid). We also start with the disc diameter $d and, given the number of columns is equal to that of rows (--n), our disc gets divided into identical tiles of edge length --l: calc(#{$d}/var(--n)).

$d: 20em; body { --l: calc(#{$d}/var(--n)); display: grid; place-content: center; grid-template: repeat(var(--n), var(--l))/ repeat(var(--n), var(--l)) }

To create the gaps in between the tiles, we use the padding approach on the .tile elements and combine the horizontal and vertical cases such that we have the padding-top for the first row is 0, the padding-left for the first column is 0, the padding-bottom for the last row is 0 and the padding-right for the last-column is 0.

padding: calc(var(--j)*#{$g}/var(--n)) /* top */ calc((var(--n) - 1 - var(--i))*#{$g}/var(--n)) /* right */ calc((var(--n) - 1 - var(--j))*#{$g}/var(--n)) /* bottom */ calc(var(--i)*#{$g}/var(--n)) /* left */

Note that we've used the row index --j for the top to bottom direction (vertical paddings) and the column index --i from the left to right direction (lateral paddings).

To get the disc shape, we again combine the horizontal and vertical cases, using the column index --i to get the x coordinate of the circular clipping path's central point and the row index --j to get its y coordinate.

clip-path: circle($r at calc(#{$r} - var(--i)*var(--l)) calc(#{$r} - var(--j)*var(--l)))

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

For a gradient background, it's again combining the horizontal and the vertical cases and taking into account that here we have no offset at this point, which means the background-size is the disc diameter $d along both axes.

background: linear-gradient(135deg, #eccc05 15%, #c26e4c, #a63959, #4e2255, #333f3d 85%) /* background-position */ calc(-1*var(--i)*var(--l)) calc(-1*var(--j)*var(--l)) / #{$d} #{$d} /* background-size */ padding-box /* background-origin */ content-box /* background-clip */

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

For an image background, we remove the second background-size value so we prevent the image from getting stretched if it's not square. We also adapt the code for attaching the image's midpoint to that of the grid from the 1D case to the 2D case:

--x: calc(#{$r} - var(--i)*var(--l)); --y: calc(#{$r} - var(--j)*var(--l)); background: url(/amur_leopard.jpg) /* background-position */ calc(50% + var(--x) - .5*var(--l)) calc(50% + var(--y) - .5*var(--l)) / #{$d} /* background-size */ padding-box /* background-origin */ content-box /* background-clip */; clip-path: circle($r at var(--x) var(--y))

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

In the incremental case, we don't have the same dimensions for all tiles, so we use auto sizing for grid-template:

body { /* same as before */ grid-template: repeat(var(--n), auto)/ repeat(var(--n), auto) }

Just like in the 1D case, we start by computing a unit edge length --u:

--u: calc(#{2*$d}/var(--n)/(var(--n) + 1))

We then set incremental dimensions along both axes for our tile elements:

width: calc((var(--i) + 1)*var(--u)); height: calc((var(--j) + 1)*var(--u))

We also need to adapt the coordinates of the clipping circle's central point to the incremental case:

clip-path: circle($r at calc(#{$r} - .5*var(--i)*(var(--i) + 1)*var(--u)) calc(#{$r} - .5*var(--j)*(var(--j) + 1)*var(--u)))

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

For a gradient background, we adapt the equal tiles version to the incremental case. This means tweaking the background-position as we did before for the incremental slices, only this time we do it along both axes, not just along one:

background: linear-gradient(135deg, #eccc05 15%, #c26e4c, #a63959, #4e2255, #333f3d 85%) /* background-position */ calc(-.5*var(--i)*(var(--i) + 1)*var(--l)) calc(-.5*var(--j)*(var(--j) + 1)*var(--l)) / #{$d} #{$d} /* background-size */ padding-box /* background-origin */ content-box /* background-clip */

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

Finally, we have the image background option for the incremental 2D case:

background: url(/amur_leopard.jpg) /* background-position */ calc(50% + var(--x) - .5*(var(--i) + 1)*var(--u)) calc(50% + var(--y) - .5*(var(--j) + 1)*var(--u)) / #{$d} /* background-size */ padding-box /* background-origin */ content-box /* background-clip */

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

There are probably more variations we could be coming up with, but we'll stop here. If you have more ideas on how to push this further, I'd love to hear about them!

The post Slice and Dice a Disc with CSS appeared first on CSS-Tricks.

Re: Pleasing Color Palettes

Css Tricks - Fri, 01/11/2019 - 11:43am

There are so many tools out there to help you pick colors. I totally get it! It's hard! When colors are done well, it's like magic. It adds a level of polish to a design that can really set it apart.

Let's look at some, then talk about this idea some more.

Here's one I just saw called Color Koala:

It spits out five colors at ya and you're off to the races.

Hue will give you some too.

There's a billion more, and they vary in approach and features, of course. Here’s a handful:

Then there are tools that focus on gradients, like UI Gradients, Web Gradients, and Shapy.

Oh! And a site that helps with text color while keeping accessibility in mind.

There are even native apps like Sip, ColorSnapper, and Frank DeLoupe that help you select colors and sometimes keep your palettes right within them.

Colors can be programatically generated.

There is no native JavaScript API for it, but it's still basically a one-liner:

See the Pen Generate New Random Hex Color with JavaScript by Chris Coyier (@chriscoyier) on CodePen.

Pleasing colors can be as well.

Generating random colors won’t guarantee pleasing palettes, especially if a bunch of random colors are paired together. PleaseJS can help build color schemes that work together. You provide it a base color and other options (like what type of color scheme) and it spits out colors for you.

See the Pen Generate Pleasing Colors by Chris Coyier (@chriscoyier) on CodePen.

Similarly, randomColor.js...

gen­er­ates at­trac­tive col­ors by de­fault. More specif­i­cally, ran­dom­Color pro­duces bright col­ors with a rea­son­ably high sat­u­ra­tion. This makes ran­dom­Color par­tic­u­larly use­ful for data vi­su­al­iza­tions and gen­er­a­tive art.

It doesn't claim to make multiple colors part of a cohesive theme aside from passing in a base hue or luminosity.

See the Pen Generate Pleasing Colors by Chris Coyier (@chriscoyier) on CodePen.

But the thing about just being handed colors is...

...they don't exactly tell you how to use them. Steve Schoger makes a point of this, rather hilariously in a blog post. This is a perfectly lovely color palette:

But if you just pick those colors and plop them onto a design, you could end up with something like this:

You might like that, but you'd be in the minority. It's not a refined design that gets out of the way and would be nice to use every day. Color usage is a bit more complicated than plopping five nice colors into a design. It's variations on those and using them in tasteful ways, like this:

Picking up Steve Schoger and Adam Wathan's book surely has some advice for you there!

The post Re: Pleasing Color Palettes appeared first on CSS-Tricks.

Piecing Together Approaches for a CSS Masonry Layout

Css Tricks - Fri, 01/11/2019 - 5:06am

Masonry layout, on the web, is when items of an uneven size are laid out such that there aren't uneven gaps. I would guess the term was coined (or at least popularized) for the web by David DeSandro because of his popular Masonry JavaScript library, which has been around since 2010.

JavaScript library. Nothing against JavaScript, but it's understandable we might not want to lean on it for doing layout. Is there anything we can do in CSS directly these days? Sorta.

Is vertical order with ragged bottoms OK?

If it is, then CSS columns will do just fine.

See the Pen Masonry with Columns by Chris Coyier (@chriscoyier) on CodePen.

Flexbox can do vertical columns with ragged endings too

But it's not quite as clever, because you'll need to set a height of some kind to get it to wrap the columns. You'll also have to be explicit about widths rather than having it decide columns for you.

But it's doable and it does auto space the gaps if there is room.

See the Pen Masonry with Flexbox by Chris Coyier (@chriscoyier) on CodePen.

Do you need a clean bottom edge? A Flexbox/JavaScript combo can help.

Jamie Perkins originally wrote this, then Janosh Riebesell re-wrote it and, now I'm porting it here.

It totally messes with the order and requires the children to be flexy about their height, but it does the trick:

See the Pen Masonry with Flexbox + JS by Chris Coyier (@chriscoyier) on CodePen.

Is horizontal line masonry OK?

If it's just the uneven brick-like look you're after, then horizontal masonry is way easier. You could probably even float stuff if you don't care about the ragged edge. If you wanna keep it a block... flexbox with allowed flex-grow is the ticket.

See the Pen Masonry with Flexbox + JS by Chris Coyier (@chriscoyier) on CodePen.

You'd think CSS grid could help

CSS grid is very amazing and useful in a CSS developer’s everyday life, but it's not really designed for masonry style layouts. CSS grid is about defining lines and placing things along those lines, where masonry is about letting elements end where they may, but still exerting some positional influence.

Balázs Sziklai has a nice example of auto-flowing grids that all stack together nicely, with pretty good horiziontal ordering:

See the Pen True Masonry with Grid Layout by Balázs Sziklai (@balazs_sziklai) on CodePen.

But you can see how strict the lines are. There is a way though!

Grid + JavaScript-manipulated row spans

Andy Barefoot wrote up a great guide. The trick is setting up repeating grid rows that are fairly short, letting the elements fall into the grid horizontally as they may, then adjusting their heights to match the grid with some fairly light math to calculate how many rows they should span.

See the Pen CSS Grid Masonry (Step 10) by Andy Barefoot (@andybarefoot) on CodePen.

Rahul Arora went down this road as well:

See the Pen Rahul Arora's Left-to-right Masonry Layout using CSS Grid by Chris Coyier (@chriscoyier) on CodePen.

DOM-shifted elements in a CSS columns layout

What people generally want is column-stacking (varied heights on elements), but with horizontal ordering. That last grid demo above handles it pretty well, but it's not the only way.

Jesse Korzan tackled it with CSS columns. It needs JavaScript as well to get it done. In this case, it shifts the elements in the DOM to order them left-to-right while providing a horizontal stack using a CSS columns layout. This introduces a bit of an accessibility problem since the visual order (left-to-right) and source order (top-to-bottom) are super different & dash; though perhaps fixable with programmatic tabindex?

There’s also the original library and variations

Float away, my pretties.

See the Pen Masonry with Masonry by Chris Coyier (@chriscoyier) on CodePen.

And it's newer, hipper verion: Colcade!

See the Pen Masonry with Colcade by Chase (@chasebank) on CodePen.

And here's MagicGrid, in which a flexbox layout is lightly manipulated with a JavaScript lib:

See the Pen Magic Grid by Chris Coyier (@chriscoyier) on CodePen.

The post Piecing Together Approaches for a CSS Masonry Layout appeared first on CSS-Tricks.

Why we need CSS subgrid

Css Tricks - Fri, 01/11/2019 - 5:03am

I’m a huge fan of CSS Grid and I use it on pretty much every project these days. However, there’s one part of it that makes things much more complicated than they really ought to be: the lack of subgrids. And in this post on the matter, Ken Bellows explains why they’d be so gosh darn useful:

But one thing still missing from the Level 1 spec is the ability to create a subgrid, a grid-item with its own grid that aligns in one or both dimensions with the parent grid. It was originally planned to be in Level 1, but the working group decided they needed more time to work out the details, so it was removed, and it will ship in CSS Grid Layout Module Level 2, which seems to be nearing completion.

There has been a lot of discussion over the last 2 years about the use cases for subgrid, how it should be implemented, and even some debate over whether you even need it. A lot of that discussion was centered around two other approaches that can handle many of the same problems as subgrid: nested grids and display: contents

I remember one of the very first websites I worked on was much like the demo that Ken uses as an example, but this was way back in 2012 and grid didn’t exist yet. Sadly, I had to write a lot more CSS than I felt was necessary to get elements in one div to line up with elements in another. Anyway, this article kinda riffs off of Rachel Andrew’s post about subgrid and what problems it would help solve which is definitely worth checking out, too.

Direct Link to ArticlePermalink

The post Why we need CSS subgrid appeared first on CSS-Tricks.

Converting Color Spaces in JavaScript

Css Tricks - Thu, 01/10/2019 - 5:07am

A challenge I faced in building an image "emojifier" was that I needed to change the color spaces of values obtained using getImageData() from RGB to HSL. I used arrays of emojis arranged by brightness and saturation, and they were HSL-based for the best matches of average pixel colors with the emojis.

In this article, we’ll study functions that will be useful for converting both opaque and alpha-enabled color values. Modern browsers currently support the color spaces RGB(A), hex, and HSL(A). The functions and notations for these are rgb(), rgba(), #rgb/#rrggbb, #rgba/#rrggbbaa, hsl(), and hsla(). Browsers have always supported built-in names like aliceblue as well.

Along the way, we’ll encounter use of some color syntaxes provided by a new Level 4 of the CSS Colors Module. For example, we now have hex with alpha as we mentioned (#rgba/#rrggbbaa) and RGB and HSL syntaxes no longer require commas (values like rgb(255 0 0) and hsl(240 100% 50%) became legal!).

Browser support for CSS Colors Level 4 isn’t universal as of this writing, so don’t expect new color syntaxes to work in Microsoft browsers or Safari if trying them in CSS.

RGB to Hex

Converting RGB to hex is merely a change of radices. We convert the red, green, and blue values from decimal to hexadecimal using toString(16). After prepending 0s to single digits and under, we can concatenate them and # to a single return statement.

function RGBToHex(r,g,b) { r = r.toString(16); g = g.toString(16); b = b.toString(16); if (r.length == 1) r = "0" + r; if (g.length == 1) g = "0" + g; if (b.length == 1) b = "0" + b; return "#" + r + g + b; } RGB in String

Alternatively, we can use a single string argument with the red, green and blue separated by commas or spaces (e.g. "rgb(255,25,2)", "rgb(255 25 2)"). Substring to eliminate rgb(, split what’s left by the ), then split that result’s first item by whichever the separator (sep) is. r, g, and b shall become local variables now. Then we use + before the split strings to convert them back to numbers before obtaining the hex values.

function RGBToHex(rgb) { // Choose correct separator let sep = rgb.indexOf(",") > -1 ? "," : " "; // Turn "rgb(r,g,b)" into [r,g,b] rgb = rgb.substr(4).split(")")[0].split(sep); let r = (+rgb[0]).toString(16), g = (+rgb[1]).toString(16), b = (+rgb[2]).toString(16); if (r.length == 1) r = "0" + r; if (g.length == 1) g = "0" + g; if (b.length == 1) b = "0" + b; return "#" + r + g + b; }

In addition, we can allow strings with channel values as percentages by adding the loop after redefining rgb. It'll strip the %s and turn what’s left into values out of 255.

function RGBToHex(rgb) { let sep = rgb.indexOf(",") > -1 ? "," : " "; rgb = rgb.substr(4).split(")")[0].split(sep); // Convert %s to 0–255 for (let R in rgb) { let r = rgb[R]; if (r.indexOf("%") > -1) rgb[R] = Math.round(r.substr(0,r.length - 1) / 100 * 255); /* Example: 75% -> 191 75/100 = 0.75, * 255 = 191.25 -> 191 */ } ... }

Now we can supply values like either of these:

  • rgb(255,25,2)
  • rgb(255 25 2)
  • rgb(50%,30%,10%)
  • rgb(50% 30% 10%)
RGBA to Hex (#rrggbbaa)

Converting RGBA to hex with the #rgba or #rrggbbaa notation follows virtually the same process as the opaque counterpart. Since the alpha (a) is normally a value between 0 and 1, we need to multiply it by 255, round the result, then convert it to hexadecimal.

function RGBAToHexA(r,g,b,a) { r = r.toString(16); g = g.toString(16); b = b.toString(16); a = Math.round(a * 255).toString(16); if (r.length == 1) r = "0" + r; if (g.length == 1) g = "0" + g; if (b.length == 1) b = "0" + b; if (a.length == 1) a = "0" + a; return "#" + r + g + b + a; }

To do this with one string (including with percentages), we can follow what we did earlier. Also note the extra step of splicing out a slash. Since CSS Colors Level 4 supports the syntax of rgba(r g b / a), this is where we allow it. Alpha values can now be percentages! This removes the 0-1-only shackles we used to have. Therefore, the for loop cycling through rgba shall include a part to wipe the % from the alpha without multiplying by 255 (when R is 3 for alpha). Soon we can use values like rgba(255 128 0 / 0.8) and rgba(100% 21% 100% / 30%)!

function RGBAToHexA(rgba) { let sep = rgba.indexOf(",") > -1 ? "," : " "; rgba = rgba.substr(5).split(")")[0].split(sep); // Strip the slash if using space-separated syntax if (rgba.indexOf("/") > -1) rgba.splice(3,1); for (let R in rgba) { let r = rgba[R]; if (r.indexOf("%") > -1) { let p = r.substr(0,r.length - 1) / 100; if (R < 3) { rgba[R] = Math.round(p * 255); } else { rgba[R] = p; } } } }

Then, where the channels are converted to hex, we adjust a to use an item of rgba[].

function RGBAToHexA(rgba) { ... let r = (+rgba[0]).toString(16), g = (+rgba[1]).toString(16), b = (+rgba[2]).toString(16), a = Math.round(+rgba[3] * 255).toString(16); if (r.length == 1) r = "0" + r; if (g.length == 1) g = "0" + g; if (b.length == 1) b = "0" + b; if (a.length == 1) a = "0" + a; return "#" + r + g + b + a; }

Now the function supports the following:

  • rgba(255,25,2,0.5)
  • rgba(255 25 2 / 0.5)
  • rgba(50%,30%,10%,0.5)
  • rgba(50%,30%,10%,50%)
  • rgba(50% 30% 10% / 0.5)
  • rgba(50% 30% 10% / 50%)
Hex to RGB

We know that the length of hex values must either be 3 or 6 (plus #). In either case, we begin each red (r), green (g), and blue (b) value with "0x" to convert them to hex. If we provide a 3-digit value, we concatenate the same value twice for each channel. If it’s a 6-digit value, we concatenate the first two for red, next two for green, and last two for blue. To get the values for the final rgb() string, we prepend the variables with + to convert them from strings back to numbers, which will yield the decimals we need.

function hexToRGB(h) { let r = 0, g = 0, b = 0; // 3 digits if (h.length == 4) { r = "0x" + h[1] + h[1]; g = "0x" + h[2] + h[2]; b = "0x" + h[3] + h[3]; // 6 digits } else if (h.length == 7) { r = "0x" + h[1] + h[2]; g = "0x" + h[3] + h[4]; b = "0x" + h[5] + h[6]; } return "rgb("+ +r + "," + +g + "," + +b + ")"; } Output RGB with %s

If we want to return rgb() using percentages, then we can modify the function to utilize an optional isPct parameter like so:

function hexToRGB(h,isPct) { let r = 0, g = 0, b = 0; isPct = isPct === true; if (h.length == 4) { r = "0x" + h[1] + h[1]; g = "0x" + h[2] + h[2]; b = "0x" + h[3] + h[3]; } else if (h.length == 7) { r = "0x" + h[1] + h[2]; g = "0x" + h[3] + h[4]; b = "0x" + h[5] + h[6]; } if (isPct) { r = +(r / 255 * 100).toFixed(1); g = +(g / 255 * 100).toFixed(1); b = +(b / 255 * 100).toFixed(1); } return "rgb(" + (isPct ? r + "%," + g + "%," + b + "%" : +r + "," + +g + "," + +b) + ")"; }

Under the last if statement, using +s will convert r, g, and b to numbers. Each toFixed(1) along with them will round the result to the nearest tenth. Additionally, we won’t have whole numbers with .0 or the decades old quirk that produces numbers like 0.30000000000000004. Therefore, in the return, we omitted the +s right before the first r, g, and b to prevent NaNs caused by the %s. Now we can use hexToRGB("#ff0",true) to get rgb(100%,100%,0%)!

Hex (#rrggbbaa) to RGBA

The procedure for hex values with alpha should again be similar with the last. We simply detect a 4- or 8-digit value (plus #) then convert the alpha and divide it by 255. To get more precise output but not long decimal numbers for alpha, we can use toFixed(3).

function hexAToRGBA(h) { let r = 0, g = 0, b = 0, a = 1; if (h.length == 5) { r = "0x" + h[1] + h[1]; g = "0x" + h[2] + h[2]; b = "0x" + h[3] + h[3]; a = "0x" + h[4] + h[4]; } else if (h.length == 9) { r = "0x" + h[1] + h[2]; g = "0x" + h[3] + h[4]; b = "0x" + h[5] + h[6]; a = "0x" + h[7] + h[8]; } a = +(a / 255).toFixed(3); return "rgba(" + +r + "," + +g + "," + +b + "," + a + ")"; } Output RGBA with %s

For a version that outputs percentages, we can do what we did in hexToRGB()—switch r, g, and b to 0–100% when isPct is true.

function hexAToRGBA(h,isPct) { let r = 0, g = 0, b = 0, a = 1; isPct = isPct === true; // Handling of digits ... if (isPct) { r = +(r / 255 * 100).toFixed(1); g = +(g / 255 * 100).toFixed(1); b = +(b / 255 * 100).toFixed(1); } a = +(a / 255).toFixed(3); return "rgba(" + (isPct ? r + "%," + g + "%," + b + "%," + a : +r + "," + +g + "," + +b + "," + a) + ")"; }

Here’s a quick fix if the alpha ought to be a percentage, too: move the statement where a is redefined above the last if statement. Then in that statement, modify a to be like r, g, and b. When isPct is true, a must also gain the %.

function hexAToRGBA(h,isPct) { ... a = +(a / 255).toFixed(3); if (isPct) { r = +(r / 255 * 100).toFixed(1); g = +(g / 255 * 100).toFixed(1); b = +(b / 255 * 100).toFixed(1); a = +(a * 100).toFixed(1); } return "rgba(" + (isPct ? r + "%," + g + "%," + b + "%," + a + "%" : +r + "," + +g + "," + +b + "," + a) + ")"; }

When we enter #7f7fff80 now, we should get rgba(127,127,255,0.502) or rgba(49.8%,49.8%,100%,50.2%).

RGB to HSL

Obtaining HSL values from RGB or hex is a bit more challenging because there’s a larger formula involved. First, we must divide the red, green, and blue by 255 to use values between 0 and 1. Then we find the minimum and maximum of those values (cmin and cmax) as well as the difference between them (delta). We need that result as part of calculating the hue and saturation. Right after the delta, let’s initialize the hue (h), saturation (s), and lightness (l).

function RGBToHSL(r,g,b) { // Make r, g, and b fractions of 1 r /= 255; g /= 255; b /= 255; // Find greatest and smallest channel values let cmin = Math.min(r,g,b), cmax = Math.max(r,g,b), delta = cmax - cmin, h = 0, s = 0, l = 0; }

Next, we need to calculate the hue, which is to be determined by the greatest channel value in cmax (or if all channels are the same). If there is no difference between the channels, the hue will be 0. If cmax is the red, then the formula will be ((g - b) / delta) % 6. If green, then (b - r) / delta + 2. Then, if blue, (r - g) / delta + 4. Finally, multiply the result by 60 (to get the degree value) and round it. Since hues shouldn’t be negative, we add 360 to it, if needed.

function RGBToHSL(r,g,b) { ... // Calculate hue // No difference if (delta == 0) h = 0; // Red is max else if (cmax == r) h = ((g - b) / delta) % 6; // Green is max else if (cmax == g) h = (b - r) / delta + 2; // Blue is max else h = (r - g) / delta + 4; h = Math.round(h * 60); // Make negative hues positive behind 360° if (h < 0) h += 360; }

All that’s left is the saturation and lightness. Let’s calculate the lightness before we do the saturation, as the saturation will depend on it. It’s the sum of the maximum and minimum channel values cut in half ((cmax + cmin) / 2). Then delta will determine what the saturation will be. If it’s 0 (no difference between cmax and cmin), then the saturation is automatically 0. Otherwise, it’ll be 1 minus the absolute value of twice the lightness minus 1 (1 - Math.abs(2 * l - 1)). Once we have these values, we must convert them to values out of 100%, so we multiply them by 100 and round to the nearest tenth. Now we can string together our hsl().

function RGBToHSL(r,g,b) { ... // Calculate lightness l = (cmax + cmin) / 2; // Calculate saturation s = delta == 0 ? 0 : delta / (1 - Math.abs(2 * l - 1)); // Multiply l and s by 100 s = +(s * 100).toFixed(1); l = +(l * 100).toFixed(1); return "hsl(" + h + "," + s + "%," + l + "%)"; } RGB in String

For one string, split the argument by comma or space, strip the %s, and localize r, g, and b like we did before.

function RGBToHSL(rgb) { let sep = rgb.indexOf(",") > -1 ? "," : " "; rgb = rgb.substr(4).split(")")[0].split(sep); for (let R in rgb) { let r = rgb[R]; if (r.indexOf("%") > -1) rgb[R] = Math.round(r.substr(0,r.length - 1) / 100 * 255); } // Make r, g, and b fractions of 1 let r = rgb[0] / 255, g = rgb[1] / 255, b = rgb[2] / 255; ... } RGBA to HSLA

Compared to what we just did to convert RGB to HSL, the alpha counterpart will be basically nothing! We just reuse the code for RGB to HSL (the multi-argument version), leave a alone, and pass a to the returned HSLA. Keep in mind it should be between 0 and 1.

function RGBAToHSLA(r,g,b,a) { // Code for RGBToHSL(r,g,b) before return ... return "hsla(" + h + "," + s + "%," +l + "%," + a + ")"; } RGBA in String

For string values, we apply the splitting and stripping logic again but use the fourth item in rgba for a. Remember the new rgba(r g b / a) syntax? We’re employing the acceptance of it as we did for RGBAToHexA(). Then the rest of the code is the normal RGB-to-HSL conversion.

function RGBAToHSLA(rgba) { let sep = rgba.indexOf(",") > -1 ? "," : " "; rgba = rgba.substr(5).split(")")[0].split(sep); // Strip the slash if using space-separated syntax if (rgba.indexOf("/") > -1) rgba.splice(3,1); for (let R in rgba) { let r = rgba[R]; if (r.indexOf("%") > -1) { let p = r.substr(0,r.length - 1) / 100; if (R < 3) { rgba[R] = Math.round(p * 255); } else { rgba[R] = p; } } } // Make r, g, and b fractions of 1 let r = rgba[0] / 255, g = rgba[1] / 255, b = rgba[2] / 255, a = rgba[3]; // Rest of RGB-to-HSL logic ... }

Wish to leave the alpha as is? Remove the else statement from the for loop.

for (let R in rgba) { let r = rgba[R]; if (r.indexOf("%") > -1) { let p = r.substr(0,r.length - 1) / 100; if (R < 3) { rgba[R] = Math.round(p * 255); } } } HSL to RGB

It takes slightly less logic to convert HSL back to RGB than the opposite way. Since we’ll use a range of 0–100 for the saturation and lightness, the first step is to divide them by 100 to values between 0 and 1. Next, we find chroma (c), which is color intensity, so that’s (1 - Math.abs(2 * l - 1)) * s. Then we use x for the second largest component (first being chroma), the amount to add to each channel to match the lightness (m), and initialize r, g, b.

function HSLToRGB(h,s,l) { // Must be fractions of 1 s /= 100; l /= 100; let c = (1 - Math.abs(2 * l - 1)) * s, x = c * (1 - Math.abs((h / 60) % 2 - 1)), m = l - c/2, r = 0, g = 0, b = 0; }

The hue will determine what the red, green, and blue should be depending on which 60° sector of the color wheel it lies.

The color wheel divided into 60° segments

Then c and x shall be assigned as shown below, leaving one channel at 0. To get the final RGB value, we add m to each channel, multiply it by 255, and round it.

function HSLToRGB(h,s,l) { ... if (0 <= h && h < 60) { r = c; g = x; b = 0; } else if (60 <= h && h < 120) { r = x; g = c; b = 0; } else if (120 <= h && h < 180) { r = 0; g = c; b = x; } else if (180 <= h && h < 240) { r = 0; g = x; b = c; } else if (240 <= h && h < 300) { r = x; g = 0; b = c; } else if (300 <= h && h < 360) { r = c; g = 0; b = x; } r = Math.round((r + m) * 255); g = Math.round((g + m) * 255); b = Math.round((b + m) * 255); return "rgb(" + r + "," + g + "," + b + ")"; } HSL in String

For the single string version, we modify the first few statements basically the same way we did for RGBToHSL(r,g,b). Remove s /= 100; and l /= 100; and we’ll use the new statements to wipe the first 4 characters and the ) for our array of HSL values, then the %s from s and l before dividing them by 100.

function HSLToRGB(hsl) { let sep = hsl.indexOf(",") > -1 ? "," : " "; hsl = hsl.substr(4).split(")")[0].split(sep); let h = hsl[0], s = hsl[1].substr(0,hsl[1].length - 1) / 100, l = hsl[2].substr(0,hsl[2].length - 1) / 100; ... }

The next handful of statements shall handle hues provided with a unit—degrees, radians, or turns. We multiply radians by 180/? and turns by 360. If the result ends up over 360, we compound modulus divide to keep it within the scope. All of this will happen before we deal with c, x, and m.

function HSLToRGB(hsl) { ... // Strip label and convert to degrees (if necessary) if (h.indexOf("deg") > -1) h = h.substr(0,h.length - 3); else if (h.indexOf("rad") > -1) h = Math.round(h.substr(0,h.length - 3) * (180 / Math.PI)); else if (h.indexOf("turn") > -1) h = Math.round(h.substr(0,h.length - 4) * 360); // Keep hue fraction of 360 if ending up over if (h >= 360) h %= 360; // Conversion to RGB begins ... }

After implementing the steps above, now the following can be safely used:

  • hsl(180 100% 50%)
  • hsl(180deg,100%,50%)
  • hsl(180deg 100% 50%)
  • hsl(3.14rad,100%,50%)
  • hsl(3.14rad 100% 50%)
  • hsl(0.5turn,100%,50%)
  • hsl(0.5turn 100% 50%)

Whew, that’s quite the flexibility!

Output RGB with %s

Similarly, we can modify this function to return percent values just like we did in hexToRGB().

function HSLToRGB(hsl,isPct) { let sep = hsl.indexOf(",") > -1 ? "," : " "; hsl = hsl.substr(4).split(")")[0].split(sep); isPct = isPct === true; ... if (isPct) { r = +(r / 255 * 100).toFixed(1); g = +(g / 255 * 100).toFixed(1); b = +(b / 255 * 100).toFixed(1); } return "rgb("+ (isPct ? r + "%," + g + "%," + b + "%" : +r + "," + +g + "," + +b) + ")"; } HSLA to RGBA

Once again, handling alphas will be a no-brainer. We can reapply the code for the original HSLToRGB(h,s,l) and add a to the return.

function HSLAToRGBA(h,s,l,a) { // Code for HSLToRGB(h,s,l) before return ... return "rgba(" + r + "," + g + "," + b + "," + a + ")"; } HSLA in String

Changing it to one argument, the way we’ll handle strings here will be not too much different than what we did earlier. A new HSLA syntax from Colors Level 4 uses (value value value / value) just like RGBA, so having the code to handle it, we’ll be able to plug in something like hsla(210 100% 50% / 0.5) here.

function HSLAToRGBA(hsla) { let sep = hsla.indexOf(",") > -1 ? "," : " "; hsla = hsla.substr(5).split(")")[0].split(sep); if (hsla.indexOf("/") > -1) hsla.splice(3,1); let h = hsla[0], s = hsla[1].substr(0,hsla[1].length - 1) / 100, l = hsla[2].substr(0,hsla[2].length - 1) / 100, a = hsla[3]; if (h.indexOf("deg") > -1) h = h.substr(0,h.length - 3); else if (h.indexOf("rad") > -1) h = Math.round(h.substr(0,h.length - 3) * (180 / Math.PI)); else if (h.indexOf("turn") > -1) h = Math.round(h.substr(0,h.length - 4) * 360); if (h >= 360) h %= 360; ... }

Furthermore, these other combinations have become possible:

  • hsla(180,100%,50%,50%)
  • hsla(180 100% 50% / 50%)
  • hsla(180deg,100%,50%,0.5)
  • hsla(3.14rad,100%,50%,0.5)
  • hsla(0.5turn 100% 50% / 50%)
RGBA with %s

Then we can replicate the same logic for outputting percentages, including alpha. If the alpha should be a percentage (searched in pctFound), here’s how we can handle it:

  1. If r, g, and b are to be converted to percentages, then a should be multiplied by 100, if not already a percentage. Otherwise, drop the %, and it’ll be added back in the return.
  2. If r, g, and b should be left alone, then remove the % from a and divide a by 100.
function HSLAToRGBA(hsla,isPct) { // Code up to slash stripping ... isPct = isPct === true; // h, s, l, a defined to rounding of r, g, b ... let pctFound = a.indexOf("%") > -1; if (isPct) { r = +(r / 255 * 100).toFixed(1); g = +(g / 255 * 100).toFixed(1); b = +(b / 255 * 100).toFixed(1); if (!pctFound) { a *= 100; } else { a = a.substr(0,a.length - 1); } } else if (pctFound) { a = a.substr(0,a.length - 1) / 100; } return "rgba("+ (isPct ? r + "%," + g + "%," + b + "%," + a + "%" : +r + ","+ +g + "," + +b + "," + +a) + ")"; } Hex to HSL

You might think this one and the next are crazier processes than the others, but they merely come in two parts with recycled logic. First, we convert the hex to RGB. That gives us the base 10s we need to convert to HSL.

function hexToHSL(H) { // Convert hex to RGB first let r = 0, g = 0, b = 0; if (H.length == 4) { r = "0x" + H[1] + H[1]; g = "0x" + H[2] + H[2]; b = "0x" + H[3] + H[3]; } else if (H.length == 7) { r = "0x" + H[1] + H[2]; g = "0x" + H[3] + H[4]; b = "0x" + H[5] + H[6]; } // Then to HSL r /= 255; g /= 255; b /= 255; let cmin = Math.min(r,g,b), cmax = Math.max(r,g,b), delta = cmax - cmin, h = 0, s = 0, l = 0; if (delta == 0) h = 0; else if (cmax == r) h = ((g - b) / delta) % 6; else if (cmax == g) h = (b - r) / delta + 2; else h = (r - g) / delta + 4; h = Math.round(h * 60); if (h < 0) h += 360; l = (cmax + cmin) / 2; s = delta == 0 ? 0 : delta / (1 - Math.abs(2 * l - 1)); s = +(s * 100).toFixed(1); l = +(l * 100).toFixed(1); return "hsl(" + h + "," + s + "%," + l + "%)"; } Hex (#rrggbbaa) to HSLA

There aren’t too many lines that change in this one. We’ll repeat what we recently did to get the alpha by converting the hex, but won’t divide it by 255 right away. First, we must get the hue, saturation, and lightness as we did in the other to-HSL functions. Then, before the ending return, we divide the alpha and set the decimal places.

function hexAToHSLA(H) { let r = 0, g = 0, b = 0, a = 1; if (H.length == 5) { r = "0x" + H[1] + H[1]; g = "0x" + H[2] + H[2]; b = "0x" + H[3] + H[3]; a = "0x" + H[4] + H[4]; } else if (H.length == 9) { r = "0x" + H[1] + H[2]; g = "0x" + H[3] + H[4]; b = "0x" + H[5] + H[6]; a = "0x" + H[7] + H[8]; } // Normal conversion to HSL ... a = (a / 255).toFixed(3); return "hsla("+ h + "," + s + "%," + l + "%," + a + ")"; } HSL to Hex

This one starts as a conversion to RGB, but there’s an extra step to the Math.round()s of converting the RGB results to hex.

function HSLToHex(h,s,l) { s /= 100; l /= 100; let c = (1 - Math.abs(2 * l - 1)) * s, x = c * (1 - Math.abs((h / 60) % 2 - 1)), m = l - c/2, r = 0, g = 0, b = 0; if (0 <= h && h < 60) { r = c; g = x; b = 0; } else if (60 <= h && h < 120) { r = x; g = c; b = 0; } else if (120 <= h && h < 180) { r = 0; g = c; b = x; } else if (180 <= h && h < 240) { r = 0; g = x; b = c; } else if (240 <= h && h < 300) { r = x; g = 0; b = c; } else if (300 <= h && h < 360) { r = c; g = 0; b = x; } // Having obtained RGB, convert channels to hex r = Math.round((r + m) * 255).toString(16); g = Math.round((g + m) * 255).toString(16); b = Math.round((b + m) * 255).toString(16); // Prepend 0s, if necessary if (r.length == 1) r = "0" + r; if (g.length == 1) g = "0" + g; if (b.length == 1) b = "0" + b; return "#" + r + g + b; } HSL in String

Even the first few lines of this function will be like those in HSLToRGB() if we changed it to accept a single string. This is how we’ve been obtaining the hue, saturation, and lightness separately in the first place. Let’s not forget the step to remove the hue label and convert to degrees, too. All of this will be in place of s /= 100; and l /= 100;.

function HSLToHex(hsl) { let sep = hsl.indexOf(",") > -1 ? "," : " "; hsl = hsl.substr(4).split(")")[0].split(sep); let h = hsl[0], s = hsl[1].substr(0,hsl[1].length - 1) / 100, l = hsl[2].substr(0,hsl[2].length - 1) / 100; // Strip label and convert to degrees (if necessary) if (h.indexOf("deg") > -1) h = h.substr(0,h.length - 3); else if (h.indexOf("rad") > -1) h = Math.round(h.substr(0,h.length - 3) * (180 / Math.PI)); else if (h.indexOf("turn") > -1) h = Math.round(h.substr(0,h.length - 4) * 360); if (h >= 360) h %= 360; ... } HSLA to Hex (#rrggbbaa)

Adding alpha to the mix, we convert a to hex and add a fourth if to prepend a 0, if necessary. You probably already familiar with this logic because we last used it in RGBAToHexA().

function HSLAToHexA(h,s,l,a) { // Repeat code from HSLToHex(h,s,l) until 3 `toString(16)`s ... a = Math.round(a * 255).toString(16); if (r.length == 1) r = "0" + r; if (g.length == 1) g = "0" + g; if (b.length == 1) b = "0" + b; if (a.length == 1) a = "0" + a; return "#" + r + g + b + a; } HSLA in String

Finally, the lines of the single argument version up to a = hsla[3] are no different than those of HSLAToRGBA().

function HSLAToHexA(hsla) { let sep = hsla.indexOf(",") > -1 ? "," : " "; hsla = hsla.substr(5).split(")")[0].split(sep); // Strip the slash if (hsla.indexOf("/") > -1) hsla.splice(3,1); let h = hsla[0], s = hsla[1].substr(0,hsla[1].length - 1) / 100, l = hsla[2].substr(0,hsla[2].length - 1) / 100, a = hsla[3]; ... } Built-in Names

To convert a named color to RGB, hex, or HSL, you might consider turning this table of 140+ names and hex values into a massive object at the start. The truth is that we really don’t need one because here’s what we can do:

  1. Create an element
  2. Give it a text color
  3. Obtain the value of that property
  4. Remove the element
  5. Return the stored color value, which will be in RGB by default

So, our function to get RGB will only be seven statements!

function nameToRGB(name) { // Create fake div let fakeDiv = document.createElement("div"); fakeDiv.style.color = name; document.body.appendChild(fakeDiv); // Get color of div let cs = window.getComputedStyle(fakeDiv), pv = cs.getPropertyValue("color"); // Remove div after obtaining desired color value document.body.removeChild(fakeDiv); return pv; }

Let’s go even further. How about we change the output to hex instead?

function nameToHex(name) { // Get RGB from named color in temporary div let fakeDiv = document.createElement("div"); fakeDiv.style.color = name; document.body.appendChild(fakeDiv); let cs = window.getComputedStyle(fakeDiv), pv = cs.getPropertyValue("color"); document.body.removeChild(fakeDiv); // Code ripped from RGBToHex() (except pv is substringed) let rgb = pv.substr(4).split(")")[0].split(","), r = (+rgb[0]).toString(16), g = (+rgb[1]).toString(16), b = (+rgb[2]).toString(16); if (r.length == 1) r = "0" + r; if (g.length == 1) g = "0" + g; if (b.length == 1) b = "0" + b; return "#" + r + g + b; }

Or, why not HSL? &#x1f609;

function nameToHSL(name) { let fakeDiv = document.createElement("div"); fakeDiv.style.color = name; document.body.appendChild(fakeDiv); let cs = window.getComputedStyle(fakeDiv), pv = cs.getPropertyValue("color"); document.body.removeChild(fakeDiv); // Code ripped from RGBToHSL() (except pv is substringed) let rgb = pv.substr(4).split(")")[0].split(","), r = rgb[0] / 255, g = rgb[1] / 255, b = rgb[2] / 255, cmin = Math.min(r,g,b), cmax = Math.max(r,g,b), delta = cmax - cmin, h = 0, s = 0, l = 0; if (delta == 0) h = 0; else if (cmax == r) h = ((g - b) / delta) % 6; else if (cmax == g) h = (b - r) / delta + 2; else h = (r - g) / delta + 4; h = Math.round(h * 60); if (h < 0) h += 360; l = (cmax + cmin) / 2; s = delta == 0 ? 0 : delta / (1 - Math.abs(2 * l - 1)); s = +(s * 100).toFixed(1); l = +(l * 100).toFixed(1); return "hsl(" + h + "," + s + "%," + l + "%)"; }

In the long run, every conversion from a name becomes a conversion from RGB after cracking the name.

Validating Colors

In all these functions, there haven’t been any measures to prevent or correct ludicrous input (say hues over 360 or percentages over 100). If we’re only manipulating pixels on a <canvas> fetched using getImageData(), validation of color values isn’t necessary before converting because they’ll be correct no matter what. If we’re creating a color conversion tool where users supply the color, then validation would be much needed.

It’s easy to handle improper input for channels as separate arguments, like this for RGB:

// Correct red if (r > 255) r = 255; else if (r < 0) r = 0;

If validating a whole string, then a regular expression is needed. For instance, this is the RGBToHex() function given a validation step with an expression:

function RGBToHex(rgb) { // Expression for rgb() syntaxes let ex = /^rgb\((((((((1?[1-9]?\d)|10\d|(2[0-4]\d)|25[0-5]),\s?)){2}|((((1?[1-9]?\d)|10\d|(2[0-4]\d)|25[0-5])\s)){2})((1?[1-9]?\d)|10\d|(2[0-4]\d)|25[0-5]))|((((([1-9]?\d(\.\d+)?)|100|(\.\d+))%,\s?){2}|((([1-9]?\d(\.\d+)?)|100|(\.\d+))%\s){2})(([1-9]?\d(\.\d+)?)|100|(\.\d+))%))\)$/i; if (ex.test(rgb)) { // Logic to convert RGB to hex ... } else { // Something to do if color is invalid } }

To test other types of values, below is a table of expressions to cover both opaque and alpha-enabled:

Color Value RegEx RGB /^rgb\((((((((1?[1-9]?\d)|10\d|(2[0-4]\d)|25[0-5]),\s?)){2}|((((1?[1-9]?\d)|10\d|(2[0-4]\d)|25[0-5])\s)){2})((1?[1-9]?\d)|10\d|(2[0-4]\d)|25[0-5]))|((((([1-9]?\d(\.\d+)?)|100|(\.\d+))%,\s?){2}|((([1-9]?\d(\.\d+)?)|100|(\.\d+))%\s){2})(([1-9]?\d(\.\d+)?)|100|(\.\d+))%))\)$/i RGBA /^rgba\((((((((1?[1-9]?\d)|10\d|(2[0-4]\d)|25[0-5]),\s?)){3})|(((([1-9]?\d(\.\d+)?)|100|(\.\d+))%,\s?){3}))|(((((1?[1-9]?\d)|10\d|(2[0-4]\d)|25[0-5])\s){3})|(((([1-9]?\d(\.\d+)?)|100|(\.\d+))%\s){3}))\/\s)((0?\.\d+)|[01]|(([1-9]?\d(\.\d+)?)|100|(\.\d+))%)\)$/i Hex /^#([\da-f]{3}){1,2}$/i Hex (with Alpha) /^#([\da-f]{4}){1,2}$/i HSL /^hsl\(((((([12]?[1-9]?\d)|[12]0\d|(3[0-5]\d))(\.\d+)?)|(\.\d+))(deg)?|(0|0?\.\d+)turn|(([0-6](\.\d+)?)|(\.\d+))rad)((,\s?(([1-9]?\d(\.\d+)?)|100|(\.\d+))%){2}|(\s(([1-9]?\d(\.\d+)?)|100|(\.\d+))%){2})\)$/i HSLA /^hsla\(((((([12]?[1-9]?\d)|[12]0\d|(3[0-5]\d))(\.\d+)?)|(\.\d+))(deg)?|(0|0?\.\d+)turn|(([0-6](\.\d+)?)|(\.\d+))rad)(((,\s?(([1-9]?\d(\.\d+)?)|100|(\.\d+))%){2},\s?)|((\s(([1-9]?\d(\.\d+)?)|100|(\.\d+))%){2}\s\/\s))((0?\.\d+)|[01]|(([1-9]?\d(\.\d+)?)|100|(\.\d+))%)\)$/i

Looking at the expressions for RGB(A) and HSL(A), you probably have big eyes right now; these were made comprehensive enough to include most of the new syntaxes from CSS Colors Level 4. Hex, on the other hand, doesn’t need expressions as long as the others because of only digit counts. In a moment, we’ll dissect these and decipher the parts. Note that case-insensitive values (/i) pass all these.

RGB /^rgb\((((((((1?[1-9]?\d)|10\d|(2[0-4]\d)|25[0-5]),\s?)){2}|((((1?[1-9]?\d)|10\d|(2[0-4]\d)|25[0-5])\s)){2})((1?[1-9]?\d)|10\d|(2[0-4]\d)|25[0-5]))|((((([1-9]?\d(\.\d+)?)|100|(\.\d+))%,\s?){2}|((([1-9]?\d(\.\d+)?)|100|(\.\d+))%\s){2})(([1-9]?\d(\.\d+)?)|100|(\.\d+))%))\)$/i

Because rgb() accepts either all integers or all percentages, both cases are covered. In the outmost group, between the ^rgb\( and \)$, there are inner groups for both integers and percentages, all comma-spaces or spaces only as separators:

  1. (((((1?[1-9]?\d)|10\d|(2[0-4]\d)|25[0-5]),\s?){2}|(((1?[1-9]?\d)|10\d|(2[0-4]\d)|25[0-5])\s){2})((1?[1-9]?\d)|10\d|(2[0-4]\d)|25[0-5]))
  2. ((((([1-9]?\d(\.\d+)?)|100|(\.\d+))%,\s?){2}|((([1-9]?\d(\.\d+)?)|100|(\.\d+))%\s){2})(([1-9]?\d(\.\d+)?)|100|(\.\d+))%)

In the first half, we accept two instances of integers for red and green from 0–99 or 111-199 ((1?[1-9]?\d)), 100–109 (10\d), 200-249 ((2[0-4]\d)), or 250–255 (25[0-5]). We couldn’t simply do \d{1,3} because values like 03 or 017 and those greater than 255 shouldn’t be allowed. After that goes the comma and optional space (,\s?). On the other side of the |, after the first {2} (which indicates two instances of integers), we check for the same thing with space separators if the left side is false. Then for blue, the same should be accepted, but without a separator.

In the other half, acceptable values for percentages, including floats, should either be 0–99, explicitly 100 and not a float, or floats under 1 with the 0 dropped. Therefore, the segment here is (([1-9]?\d(\.\d+)?)|100|(\.\d+)), and it appears three times; twice with separator (,\s?){2}, %\s){2}), once without.

It is legal to use percentages without space separators (rgb(100%50%10%) for instance) in CSS, but the functions we wrote don’t support that. The same goes for rgba(100%50%10%/50%), hsl(40 100%50%), and hsla(40 100%50%/0.5). This could very well be a plus for code golfing and minification!

RGBA /^rgba\((((((((1?[1-9]?\d)|10\d|(2[0-4]\d)|25[0-5]),\s?)){3})|(((([1-9]?\d(\.\d+)?)|100|(\.\d+))%,\s?){3}))|(((((1?[1-9]?\d)|10\d|(2[0-4]\d)|25[0-5])\s){3})|(((([1-9]?\d(\.\d+)?)|100|(\.\d+))%\s){3}))\/\s)((0?\.\d+)|[01]|(([1-9]?\d(\.\d+)?)|100|(\.\d+))%)\)$/i

The next expression is very similar to the pervious, but three instances of integers (((((1?[1-9]?\d)|10\d|(2[0-4]\d)|25[0-5]),\s?){3})) or percentages ((((([1-9]?\d(\.\d+)?)|100|(\.\d+))%,\s?){3})), plus comma optional space are checked. Otherwise, it looks for the same thing but with space separators, plus a slash and space (\/\s) after the blue. Next to that is ((0?\.\d+)|[01]|(([1-9]?\d(\.\d+)?)|100|(\.\d+))%) where we accept floats with or without the first 0 ((0?\.\d+)), 0 or 1 ([01]) on the dot, or 0–100% ((([1-9]?\d(\.\d+)?)|100|(\.\d+))%).

Hex with Alpha // #rgb/#rrggbb /^#([\da-f]{3}){1,2}$/i // #rgba/#rrggbbaa /^#([\da-f]{4}){1,2}$/i

For both hex—with and without alpha—instances of numbers or letters a–f ([\da-f]) are accepted. Then one or two instances of this are counted for either short or longhand values supplied (#rgb or #rrggbb). As an illustration, we have this same short pattern: /^#([\da-f]{n}){1,2}$/i. Simply change n to 3 or 4.

HSL and HSLA // HSL /^hsl\((((((\[12]?[1-9]?\d)|[12]0\d|(3[0-5]\d))(\.\d+)?)|(\.\d+))(deg)?|(0|0?\.\d+)turn|(([0-6\\.\d+)?)|(\.\d+))rad)((,\s?(([1-9]?\d(\.\d+)?)|100|(\.\d+))%){2}|(\s(([1-9]?\d(\.\d+)?)|100|(\.\d+))%){2})\)$/i // HSLA /^hsla\((((((\[12]?[1-9]?\d)|[12]0\d|(3[0-5]\d))(\.\d+)?)|(\.\d+))(deg)?|(0|0?\.\d+)turn|(([0-6\\.\d+)?)|(\.\d+))rad)(((,\s?(([1-9]?\d(\.\d+)?)|100|(\.\d+))%){2},\s?)|((\s(([1-9]?\d(\.\d+)?)|100|(\.\d+))%){2}\s\/\s))((0?\.\d+)|[01]|(([1-9]?\d(\.\d+)?)|100|(\.\d+))%)\)$/i

After the \( in both expressions for HSL and HSLA, this large chunk is for the hue:

(((((\[12]?[1-9]?\d)|[12]0\d|(3[0-5]\d))(\.\d+)?)|(\.\d+))(deg)?|(0|0?\.\d+)turn|(([0-6\\.\d+)?)|(\.\d+))rad)

([12]?[1-9]?\d) covers 0–99, 110–199, and 210–299. [12]0\d covers 110–109 and 200–209. Then (3[0-5]\d) takes care of 300–359. The reason for this division of ranges is similar to that of integers in the rgb() syntax: ruling out zeros coming first and values greater than the maximum. Since hues can be floating point numbers, the first (\.\d+)? is for that.

Next to the | after the aforementioned segment of code, the second (\.\d+) is for floats without a leading zero.

Now let’s move up a level and decipher the next small chunk:

(deg)?|(0|0?\.\d+)turn|((\[0-6\\.\d+)?)|(\.\d+))rad

This contains the labels we can use for the hue—degrees, turns, or radians. We can include all or none of deg. Values in turn must be under 1. For radians, we can accept any float between 0–7. We do know, however, that one 360° turn is 2?, and it stops approximately at 6.28. You may think 6.3 and over shouldn’t be accepted. Because 2? is an irrational number, it would be too messy for this example to try to satisfy every decimal place provided by the JavaScript console. Besides, we have this snippet in our HSLTo_() functions as a second layer of security if hues 360° or over were to happen:

// Keep hue fraction of 360 if ending up over if (h >= 360) h %= 360;

Now let’s move up a level and decipher the second chunk:

(,\s?(([1-9]?\d(\.\d+)?)|100|(\.\d+))%){2}

We’re counting two instances of comma-space-percentages for the saturation and lightness (space optional). In the group after the ,\s?, we test for values 0–99 with or without decimal points (([1-9]?\d(\.\d+)?)), exactly 100, or floats under 1 without the leading 0 ((\.\d+)).

The last part the HSL expression, before the ending (\)$/i), is a similar expression if spaces are the only separator:

(\s(([1-9]?\d(\.\d+)?)|100|(\.\d+))%){2}

\s is in the beginning instead of ,\s?. Then in the HSLA expression, this same chunk is inside another group with ,\s? after its {2}.

((,\s?(([1-9]?\d(\.\d+)?)|100|(\.\d+))%){2},\s?)

That counts the comma-space between the lightness and alpha. Then if we have spaces as separators, we need to check for a space-slash-space (\s\/\s) after counting two instances of space and a percentage.

((\s(([1-9]?\d(\.\d+)?)|100|(\.\d+))%){2}\s\/\s))

After that, we have this left to check the alpha value:

(((0?\.\d+)|[01])|(([1-9]?\d(\.\d+)?)|100|(\.\d+))%)

Matches for (0?\.\d+) include floats under 1 with or without the leading 0, 0 or 1 for [01], and 0–100%.

Conclusion

If your current challenge is to convert one color space to another, you now have some ideas on how to approach it. Because it would be tiresome to walk through converting every color space ever invented in one post, we discussed the most practical and browser-supported ones. If you’d like to go beyond supported color spaces (say CMYK, XYZ, or CIE L*a*b*), EasyRGB) provides an amazing set of code-ready formulas.

To see all the conversions demonstrated here, I’ve set up a CodePen demo that shows inputs and outputs in a table. You can try different colors in lines 2–10 and see the complete functions in the JavaScript panel.

See the Pen Color Conversion by Jon Kantner (@jkantner) on CodePen.

The post Converting Color Spaces in JavaScript appeared first on CSS-Tricks.

Algorithmic Layouts

Css Tricks - Thu, 01/10/2019 - 5:06am

Don't miss this video by Heydon that digs into CSS layouts. It's great how he combines fundamental knowledge, like the way elements flow, wrap, and can have margin with new layout methods like flexbox and grid (with specific examples). Of particular note is the clear demonstration of how flexbox and grid help avoid the need to constantly intervene with media queries in order to affect responsive layouts.

So, in place of this...

.sidebar { float: left; width: 20rem; } .not-sidebar { float-right: calc(100% - 20rem); } @media (max-width: 40rem) { .sidebar, .not-sidebar { float: none; width: auto. } }

...something like this:

/* Parent container */ .with-sidebar { display: flex; flex-wrap: wrap; } .sidebar { flex-basis: 20rem; flex-grow: 1; } .not-sidebar { min-width: 50%; flex-grow: 600; }

This isn't a one-off video either, Heydon's channel has videos on making unusual shapes and custom properties as well.

Direct Link to ArticlePermalink

The post Algorithmic Layouts appeared first on CSS-Tricks.

Building Responsive WordPress Forms

Css Tricks - Thu, 01/10/2019 - 5:04am

Within the arsenal of every WordPress developer exists a toolbox of plugins used to implement key features on a website. Forms, up until now, have been a point of contention for most developers, given that no form plugins have offered seamless integration with existing website code. Therefore, forms often become an alien chunk of code requiring custom and time-consuming stylization.

Now there’s a solution: WS Form

WS Form is a developer-focused WordPress form plugin, which outputs framework-ready, responsive HTML5 code. It allows you to rapidly create forms using an innovative layout editor and a plethora of development features.

Front-End, Framework-Compatible HTML from a Layout Editor

If you’re developing or implementing a theme using Bootstrap (versions 3 & 4) or Foundation (versions 5, 6 & 6.4+), WS Form will output code that is native to those frameworks. For themes that do not use those frameworks, a fallback framework is included that is fully responsive and easy for developers to style.

The WS Form layout editor allows you to edit your form at any breakpoint. Form elements are dragged and dropped into the form, and all responsive CSS classes are handled for you. For developers wanting additional control, each field type comes with a vast array of settings, including the ability to add your own wrapper and field-level classes.

And within WS Form, time travel is real. The undo history feature allows you to step back to any point in your form development and continue from that point forward.

Introducing the First Form Debug Console

WS Form is the first WordPress form plugin to offer a dedicated debug console for developers.

A time-consuming task, when developing any form, is having to repeatedly populate a form to test it. WS Form is the first WordPress form plugin to offer the ability to automatically populate a form. Simply click "Populate" in the debug console, and the form will be pre-populated with different sample data each time. This dramatically speeds up development time, particularly with larger, multi-tab forms.

The console provides per form instance activity and error logging, as well as the ability to reload a form while still on the same web page.

Extensive HTML5 Input Type Support

WS Form includes settings for all form input types. Settings include everything from default values and placeholder text to custom validation messages and datalists. In addition to elementary HTML5 input types, WS Form offers additional fields, such as reCAPTCHA, signatures, and even e-commerce and payment buttons.

Some HTML5 input types, such as date and color selectors, are still not supported in all web browsers. WS Form overcomes this obstacle by checking for native support, and if unavailable, a suitable alternative component is loaded. You have the option of loading that component from your web server or from a CDN.

See the Field Types

Limitless Conditional Logic

Conditional logic allows you to make a form interactive and improve usability. For example, you could opt to only show shipping address fields if a checkbox is checked, or you could show an error message if a password confirmation does not match.

WS Form comes with an extensive array of options when creating if, then, and else conditions at form, tab, section, and field levels. Furthermore, conditional options are context sensitive, so, for example, color fields allow you to fire behavior if the hue or lightness of that field matches specified conditions. WS Form even allows you to fire actions, such as sending an email or showing a message, if any condition is met. This could be useful for automatically saving a form as a user steps through tabs on a form.

An Ever-Expanding Library of Form Actions

WS Form actions are fired whenever a form is saved or submitted by a user. Actions can also be fired using conditional logic.

The actions include:

  • Sending emails
  • Showing messages (e.g., a thank you message)
  • Running JavaScript
  • Firing a WordPress hook (actions or filters)
  • Initiating WordPress GDPR functionality, such as a data export or erasure request
  • Redirecting

See the Actions

Try it Today

Building a WordPress form in WS Form means you can rapidly prototype and implement forms. With responsive HTML5 code, automatic framework compatibility, and advanced conditional logic, just to name a few of the features, WS Form is changing the way WordPress forms can enhance and empower a website.

Use coupon code CSST20 to receive 20% off any WS Form PRO product!

Try a Demo

The post Building Responsive WordPress Forms appeared first on CSS-Tricks.

New ES2018 Features Every JavaScript Developer Should Know

Css Tricks - Wed, 01/09/2019 - 5:19am

The ninth edition of the ECMAScript standard, officially known as ECMAScript 2018 (or ES2018 for short), was released in June 2018. Starting with ES2016, new versions of ECMAScript specifications are released yearly rather than every several years and add fewer features than major editions used to. The newest edition of the standard continues the yearly release cycle by adding four new RegExp features, rest/spread properties, asynchronous iteration, and Promise.prototype.finally. Additionally, ES2018 drops the syntax restriction of escape sequences from tagged templates.

These new changes are explained in the subsections that follow.

The Rest/Spread Properties

One of the most interesting features added to ES2015 was the spread operator. This operator makes copying and merging arrays a lot simpler. Rather than calling the concat() or slice() method, you could use the ... operator:

const arr1 = [10, 20, 30]; // make a copy of arr1 const copy = [...arr1]; console.log(copy); // ? [10, 20, 30] const arr2 = [40, 50]; // merge arr2 with arr1 const merge = [...arr1, ...arr2]; console.log(merge); // ? [10, 20, 30, 40, 50]

The spread operator also comes in handy in situations where an array must be passed in as separate arguments to a function. For example:

const arr = [10, 20, 30] // equivalent to // console.log(Math.max(10, 20, 30)); console.log(Math.max(...arr)); // ? 30

ES2018 further expands this syntax by adding spread properties to object literals. With the spread properties you can copy own enumerable properties of an object onto a new object. Consider the following example:

const obj1 = { a: 10, b: 20 }; const obj2 = { ...obj1, c: 30 }; console.log(obj2); // ? {a: 10, b: 20, c: 30}

In this code, the ... operator is used to retrieve the properties of obj1 and assign them to obj2. Prior to ES2018, attempting to do so would throw an error. If there are multiple properties with the same name, the property that comes last will be used:

const obj1 = { a: 10, b: 20 }; const obj2 = { ...obj1, a: 30 }; console.log(obj2); // ? {a: 30, b: 20}

Spread properties also provide a new way to merge two or more objects, which can be used as an alternative to the Object.assign() method:

const obj1 = {a: 10}; const obj2 = {b: 20}; const obj3 = {c: 30}; // ES2018 console.log({...obj1, ...obj2, ...obj3}); // ? {a: 10, b: 20, c: 30} // ES2015 console.log(Object.assign({}, obj1, obj2, obj3)); // ? {a: 10, b: 20, c: 30}

Note, however, that spread properties do not always produce the same result as Object.assign(). Consider the following code:

Object.defineProperty(Object.prototype, 'a', { set(value) { console.log('set called!'); } }); const obj = {a: 10}; console.log({...obj}); // ? {a: 10} console.log(Object.assign({}, obj)); // ? set called! // ? {}

In this code, the Object.assign() method executes the inherited setter property. Conversely, the spread properties simply ignore the setter.

It's important to remember that spread properties only copy enumerable properties. In the following example, the type property won’t show up in the copied object because its enumerable attribute is set to false:

const car = { color: 'blue' }; Object.defineProperty(car, 'type', { value: 'coupe', enumerable: false }); console.log({...car}); // ? {color: "blue"}

Inherited properties are ignored even if they are enumerable:

const car = { color: 'blue' }; const car2 = Object.create(car, { type: { value: 'coupe', enumerable: true, } }); console.log(car2.color); // ? blue console.log(car2.hasOwnProperty('color')); // ? false console.log(car2.type); // ? coupe console.log(car2.hasOwnProperty('type')); // ? true console.log({...car2}); // ? {type: "coupe"}

In this code, car2 inherits the color property from car. Because spread properties only copy the own properties of an object, color is not included in the return value.

Keep in mind that spread properties can only make a shallow copy of an object. If a property holds an object, only the reference to the object will be copied:

const obj = {x: {y: 10}}; const copy1 = {...obj}; const copy2 = {...obj}; console.log(copy1.x === copy2.x); // ? true

The x property in copy1 refers to the same object in memory that x in copy2 refers to, so the strict equality operator returns true.

Another useful feature added to ES2015 was rest parameters, which enabled JavaScript programmers to use ... to represent values as an array. For example:

const arr = [10, 20, 30]; const [x, ...rest] = arr; console.log(x); // ? 10 console.log(rest); // ? [20, 30]

Here, the first item in arr is assigned to x, and remaining elements are assigned to the rest variable. This pattern, called array destructuring, became so popular that the Ecma Technical Committee decided to bring a similar functionality to objects:

const obj = { a: 10, b: 20, c: 30 }; const {a, ...rest} = obj; console.log(a); // ? 10 console.log(rest); // ? {b: 20, c: 30}

This code uses the rest properties in a destructuring assignment to copy the remaining own enumerable properties into a new object. Note that rest properties must always appear at the end of the object, otherwise an error is thrown:

const obj = { a: 10, b: 20, c: 30 }; const {...rest, a} = obj; // ? SyntaxError: Rest element must be last element

Also keep in mind that using multiple rest syntaxes in an object causes an error, unless they are nested:

const obj = { a: 10, b: { x: 20, y: 30, z: 40 } }; const {b: {x, ...rest1}, ...rest2} = obj; // no error const {...rest, ...rest2} = obj; // ? SyntaxError: Rest element must be last element Support for Rest/Spread Properties Chrome Firefox Safari Edge 60 55 11.1 No Chrome Android Firefox Android iOS Safari Edge Mobile Samsung Internet Android Webview 60 55 11.3 No 8.2 60

Node.js:

  • 8.0.0 (requires the --harmony runtime flag)
  • 8.3.0 (full support)
Asynchronous Iteration

Iterating over a collection of data is an important part of programming. Prior to ES2015, JavaScript provided statements such as for, for...in, and while, and methods such as map(), filter(), and forEach() for this purpose. To enable programmers to process the elements in a collection one at a time, ES2015 introduced the iterator interface.

An object is iterable if it has a Symbol.iterator property. In ES2015, strings and collections objects such as Set, Map, and Array come with a Symbol.iterator property and thus are iterable. The following code gives an example of how to access the elements of an iterable one at a time:

const arr = [10, 20, 30]; const iterator = arr[Symbol.iterator](); console.log(iterator.next()); // ? {value: 10, done: false} console.log(iterator.next()); // ? {value: 20, done: false} console.log(iterator.next()); // ? {value: 30, done: false} console.log(iterator.next()); // ? {value: undefined, done: true}

Symbol.iterator is a well-known symbol specifying a function that returns an iterator. The primary way to interact with an iterator is the next() method. This method returns an object with two properties: value and done. The value property contains the value of the next element in the collection. The done property contains either true or false denoting whether or not the end of the collection has reached.

By default, a plain object is not iterable, but it can become iterable if you define a Symbol.iterator property on it, as in this example:

const collection = { a: 10, b: 20, c: 30, [Symbol.iterator]() { const values = Object.keys(this); let i = 0; return { next: () => { return { value: this[values[i++]], done: i > values.length } } }; } }; const iterator = collection[Symbol.iterator](); console.log(iterator.next()); // ? {value: 10, done: false} console.log(iterator.next()); // ? {value: 20, done: false} console.log(iterator.next()); // ? {value: 30, done: false} console.log(iterator.next()); // ? {value: undefined, done: true}

This object is iterable because it defines a Symbol.iterator property. The iterator uses the Object.keys() method to get an array of the object's property names and then assigns it to the values constant. It also defines a counter variable and gives it an initial value of 0. When the iterator is executed it returns an object that contains a next() method. Each time the next() method is called, it returns a {value, done} pair, with value holding the next element in the collection and done holding a Boolean indicating if the iterator has reached the need of the collection.

While this code works perfectly, it’s unnecessarily complicated. Fortunately, using a generator function can considerably simplify the process:

const collection = { a: 10, b: 20, c: 30, [Symbol.iterator]: function * () { for (let key in this) { yield this[key]; } } }; const iterator = collection[Symbol.iterator](); console.log(iterator.next()); // ? {value: 10, done: false} console.log(iterator.next()); // ? {value: 20, done: false} console.log(iterator.next()); // ? {value: 30, done: false} console.log(iterator.next()); // ? {value: undefined, done: true}

Inside this generator, a for...in loop is used to enumerate over the collection and yield the value of each property. The result is exactly the same as the previous example, but it’s greatly shorter.

A downside of iterators is that they are not suitable for representing asynchronous data sources. ES2018’s solution to remedy that is asynchronous iterators and asynchronous iterables. An asynchronous iterator differs from a conventional iterator in that, instead of returning a plain object in the form of {value, done}, it returns a promise that fulfills to {value, done}. An asynchronous iterable defines a Symbol.asyncIterator method (instead of Symbol.iterator) that returns an asynchronous iterator.

An example should make this clearer:

const collection = { a: 10, b: 20, c: 30, [Symbol.asyncIterator]() { const values = Object.keys(this); let i = 0; return { next: () => { return Promise.resolve({ value: this[values[i++]], done: i > values.length }); } }; } }; const iterator = collection[Symbol.asyncIterator](); console.log(iterator.next().then(result => { console.log(result); // ? {value: 10, done: false} })); console.log(iterator.next().then(result => { console.log(result); // ? {value: 20, done: false} })); console.log(iterator.next().then(result => { console.log(result); // ? {value: 30, done: false} })); console.log(iterator.next().then(result => { console.log(result); // ? {value: undefined, done: true} }));

Note that it’s not possible to use an iterator of promises to achieve the same result. Although a normal, synchronous iterator can asynchronously determine the values, it still needs to determine the state of "done" synchronously.

Again, you can simplify the process by using a generator function, as shown below:

const collection = { a: 10, b: 20, c: 30, [Symbol.asyncIterator]: async function * () { for (let key in this) { yield this[key]; } } }; const iterator = collection[Symbol.asyncIterator](); console.log(iterator.next().then(result => { console.log(result); // ? {value: 10, done: false} })); console.log(iterator.next().then(result => { console.log(result); // ? {value: 20, done: false} })); console.log(iterator.next().then(result => { console.log(result); // ? {value: 30, done: false} })); console.log(iterator.next().then(result => { console.log(result); // ? {value: undefined, done: true} }));

Normally, a generator function returns a generator object with a next() method. When next() is called it returns a {value, done} pair whose value property holds the yielded value. An async generator does the same thing except that it returns a promise that fulfills to {value, done}.

An easy way to iterate over an iterable object is to use the for...of statement, but for...of doesn't work with async iterables as value and done are not determined synchronously. For this reason, ES2018 provides the for...await...of statement. Let’s look at an example:

const collection = { a: 10, b: 20, c: 30, [Symbol.asyncIterator]: async function * () { for (let key in this) { yield this[key]; } } }; (async function () { for await (const x of collection) { console.log(x); } })(); // logs: // ? 10 // ? 20 // ? 30

In this code, the for...await...of statement implicitly calls the Symbol.asyncIterator method on the collection object to get an async iterator. Each time through the loop, the next() method of the iterator is called, which returns a promise. Once the promise is resolved, the value property of the resulting object is read to the x variable. The loop continues until the done property of the returned object has a value of true.

Keep in mind that the for...await...of statement is only valid within async generators and async functions. Violating this rule results in a SyntaxError.

The next() method may return a promise that rejects. To gracefully handle a rejected promise, you can wrap the for...await...of statement in a try...catch statement, like this:

const collection = { [Symbol.asyncIterator]() { return { next: () => { return Promise.reject(new Error('Something went wrong.')) } }; } }; (async function() { try { for await (const value of collection) {} } catch (error) { console.log('Caught: ' + error.message); } })(); // logs: // ? Caught: Something went wrong. Support for Asynchronous Iterators Chrome Firefox Safari Edge 63 57 12 No Chrome Android Firefox Android iOS Safari Edge Mobile Samsung Internet Android Webview 63 57 12 No 8.2 63

Node.js:

  • 8.10.0 (requires the --harmony_async_iteration flag)
  • 10.0.0 (full support)
Promise.prototype.finally

Another exciting addition to ES2018 is the finally() method. Several JavaScript libraries had previously implemented a similar method, which proved useful in many situations. This encouraged the Ecma Technical Committee to officially add finally() to the specification. With this method, programmers will be able to execute a block of code regardless of the promise's fate. Let’s look at a simple example:

fetch('https://www.google.com') .then((response) => { console.log(response.status); }) .catch((error) => { console.log(error); }) .finally(() => { document.querySelector('#spinner').style.display = 'none'; });

The finally() method comes in handy when you need to do some clean up after the operation has finished regardless of whether or not it succeeded. In this code, the finally() method simply hides the loading spinner after the data is fetched and processed. Instead of duplicating the final logic in the then() and catch() methods, the code registers a function to be executed once the promise is either fulfilled or rejected.

You could achieve the same result by using promise.then(func, func) rather than promise.finally(func), but you would have to repeat the same code in both fulfillment handler and rejection handler, or declare a variable for it:

fetch('https://www.google.com') .then((response) => { console.log(response.status); }) .catch((error) => { console.log(error); }) .then(final, final); function final() { document.querySelector('#spinner').style.display = 'none'; }

As with then() and catch(), the finally() method always returns a promise, so you can chain more methods. Normally, you want to use finally() as the last chain, but in certain situations, such as when making a HTTP request, it’s a good practice to chain another catch() to deal with errors that may occur in finally().

Support for Promise.prototype.finally Chrome Firefox Safari Edge 63 58 11.1 18 Chrome Android Firefox Android iOS Safari Edge Mobile Samsung Internet Android Webview 63 58 11.1 No 8.2 63

Node.js:

10.0.0 (full support)

New RegExp Features

ES2018 adds four new features to the RegExp object, which further improves JavaScript’s string processing capabilities. These features are as follows:

  • s (dotAll) flag
  • Named capture groups
  • Lookbehind assertions
  • Unicode property escapes
s (dotAll) Flag

The dot (.) is a special character in a regular expression pattern that matches any character except line break characters such as line feed (\n) or carriage return (\r). A workaround to match all characters including line breaks is to use a character class with two opposite shorthands such as [\d\D]. This character class tells the regular expression engine to find a character that’s either a digit (\d) or a non-digit (\D). As a result, it matches any character:

console.log(/one[\d\D]two/.test('one\ntwo')); // ? true

ES2018 introduces a mode in which the dot can be used to achieve the same result. This mode can be activated on per-regex basis by using the s flag:

console.log(/one.two/.test('one\ntwo')); // ? false console.log(/one.two/s.test('one\ntwo')); // ? true

The benefit of using a flag to opt in to the new behavior is backwards compatibility. So existing regular expression patterns that use the dot character are not affected.

Named Capture Groups

In some regular expression patterns, using a number to reference a capture group can be confusing. For example, take the regular expression /(\d{4})-(\d{2})-(\d{2})/ which matches a date. Because date notation in American English is different from British English, it’s hard to know which group refers to the day and which group refers to the month:

const re = /(\d{4})-(\d{2})-(\d{2})/; const match= re.exec('2019-01-10'); console.log(match[0]); // ? 2019-01-10 console.log(match[1]); // ? 2019 console.log(match[2]); // ? 01 console.log(match[3]); // ? 10

ES2018 introduces named capture groups which uses the (?<name>...) syntax. So, the pattern to match a date can be written in a less ambiguous manner:

const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/; const match = re.exec('2019-01-10'); console.log(match.groups); // ? {year: "2019", month: "01", day: "10"} console.log(match.groups.year); // ? 2019 console.log(match.groups.month); // ? 01 console.log(match.groups.day); // ? 10

You can recall a named capture group later in the pattern by using the \k<name> syntax. For example, to find consecutive duplicate words in a sentence, you can use /\b(?<dup>\w+)\s+\k<dup>\b/:

const re = /\b(?<dup>\w+)\s+\k<dup>\b/; const match = re.exec('Get that that cat off the table!'); console.log(match.index); // ? 4 console.log(match[0]); // ? that that

To insert a named capture group into the replacement string of the replace() method, you will need to use the $<name> construct. For example:

const str = 'red & blue'; console.log(str.replace(/(red) & (blue)/, '$2 & $1')); // ? blue & red console.log(str.replace(/(?<red>red) & (?<blue>blue)/, '$<blue> & $<red>')); // ? blue & red Lookbehind Assertions

ES2018 brings lookbehind assertions to JavaScript, which have been available in other regex implementations for years. Previously, JavaScript only supported lookahead assertions. A lookbehind assertion is denoted by (?<=...), and enables you to match a pattern based on the substring that precedes the pattern. For example, if you want to match the price of a product in dollar, pound, or euro without capturing the currency symbol, you can use /(?<=\$|£|€)\d+(\.\d*)?/:

const re = /(?<=\$|£|€)\d+(\.\d*)?/; console.log(re.exec('199')); // ? null console.log(re.exec('$199')); // ? ["199", undefined, index: 1, input: "$199", groups: undefined] console.log(re.exec('€50')); // ? ["50", undefined, index: 1, input: "€50", groups: undefined]

There is also a negative version of lookbehind, which is denoted by (?<!...). A negative lookbehind allows you to match a pattern only if it is not preceded by the pattern within the lookbehind. For example, the pattern /(?<!un)available/ matches the word available if it does not have a "un" prefix:

const re = /(?<!un)available/; console.log(re.exec('We regret this service is currently unavailable')); // ? null console.log(re.exec('The service is available')); // ? ["available", index: 15, input: "The service is available", groups: undefined] Unicode Property Escapes

ES2018 provides a new type of escape sequence known as Unicode property escape, which provides support for full Unicode in regular expressions. Suppose you want to match the Unicode character ? in a string. Although ? is considered a number, you can’t match it with the \d shorthand character class because it only supports ASCII [0-9] characters. Unicode property escapes, on the other hand, can be used to match any decimal number in Unicode:

const str = '?'; console.log(/\d/u.test(str)); // ? false console.log(/\p{Number}/u.test(str)); // ? true

Similarly, if you want to match any Unicode word alphabetic character, you can use \p{Alphabetic}:

const str = '?'; console.log(/\p{Alphabetic}/u.test(str)); // ? true // the \w shorthand cannot match ? console.log(/\w/u.test(str)); // ? false

There is also a negated version of \p{...}, which is denoted by \P{...}:

console.log(/\P{Number}/u.test('?')); // ? false console.log(/\P{Number}/u.test('?')); // ? true console.log(/\P{Alphabetic}/u.test('?')); // ? true console.log(/\P{Alphabetic}/u.test('?')); // ? false

In addition to Alphabetic and Number, there are several more properties that can be used in Unicode property escapes. You can find a list of supported Unicode properties in the current specification proposal.

Support for New RegExp Features Chrome Firefox Safari Edge s (dotAll) Flag 62 No 11.1 No Named Capture Groups 64 No 11.1 No Lookbehind Assertions 62 No No No Unicode Property Escapes 64 No 11.1 No Chrome (Android) Firefox (Android) iOS Safari Edge Mobile Samsung Internet Android Webview s (dotAll) Flag 62 No 11.3 No 8.2 62 Named Capture Groups 64 No 11.3 No No 64 Lookbehind Assertions 62 No No No 8.2 62 Unicode Property Escapes 64 No 11.3 No No 64

Node.js:

  • 8.3.0 (requires the --harmony runtime flag)
  • 8.10.0 (support for s (dotAll) flag and lookbehind assertions)
  • 10.0.0 (full support)
Template Literal Revision

When a template literal is immediately preceded by an expression, it is called a tagged template literal. A tagged template comes in handy when you want to parse a template literal with a function. Consider the following example:

function fn(string, substitute) { if(substitute === 'ES6') { substitute = 'ES2015' } return substitute + string[1]; } const version = 'ES6'; const result = fn`${version} was a major update`; console.log(result); // ? ES2015 was a major update

In this code, a tag expression — which is a regular function — is invoked and passed the template literal. The function simply modifies the dynamic part of the string and returns it.

Prior to ES2018, tagged template literals had syntactic restrictions related to escape sequences. A backslash followed by certain sequence of characters were treated as special characters: a \x interpreted as a hex escape, a \u interpreted as a unicode escape, and a \ followed by a digit interpreted as an octal escape. As a result, strings such as "C:\xxx\uuu" or "\ubuntu" were considered invalid escape sequences by the interpreter and would throw a SyntaxError.

ES2018 removes these restrictions from tagged templates and instead of throwing an error, represents invalid escape sequences as undefined:

function fn(string, substitute) { console.log(substitute); // ? escape sequences: console.log(string[1]); // ? undefined } const str = 'escape sequences:'; const result = fn`${str} \ubuntu C:\xxx\uuu`;

Keep in mind that using illegal escape sequences in a regular template literal still causes an error:

const result = `\ubuntu`; // ? SyntaxError: Invalid Unicode escape sequence Support for Template Literal Revision Chrome Firefox Safari Edge 62 56 11 No Chrome Android Firefox Android iOS Safari Edge Mobile Samsung Internet Android Webview 62 56 11 No 8.2 62

Node.js:

  • 8.3.0 (requires the --harmony runtime flag)
  • 8.10.0 (full support)
Wrapping up

We’ve taken a good look at several key features introduced in ES2018 including asynchronous iteration, rest/spread properties, Promise.prototype.finally(), and additions to the RegExp object. Although some of these features are not fully implemented by some browser vendors yet, they can still be used today thanks to JavaScript transpilers such as Babel.

ECMAScript is rapidly evolving and new features are being introduced every so often, so check out the list of finished proposals for the full scope of what’s new. Are there any new features you’re particularly excited about? Share them in the comments!

The post New ES2018 Features Every JavaScript Developer Should Know appeared first on CSS-Tricks.

Toggling Animations On and Off

Css Tricks - Wed, 01/09/2019 - 5:09am

A nicely detailed tutorial by Kirupa that gets into how you might provide a UI with persisted options that control whether animations run or not.

The trick is custom properties that control the movement:

body { --toggle: 0; --playState: "paused"; }

Which are used within animations and transitions:

.animation { animation: bobble 2s infinite; animation-play-state: var(--playState); } .transition { transition: transform calc(var(--toggle) * .15s) ease-in-out; }

And toggle-able by JavaScript:

// stop animation document.body.style.setProperty("--toggle", "0"); document.body.style.setProperty("--playState", "paused"); // play animation document.body.style.setProperty("--toggle", "1"); document.body.style.setProperty("--playState", "running");

Then get into using the media query to test for reduced motion off the bat, and storing the preferred value in localStorage.

Direct Link to ArticlePermalink

The post Toggling Animations On and Off appeared first on CSS-Tricks.

Styling a Web Component

Css Tricks - Tue, 01/08/2019 - 9:02am

This confused me for a bit here so I'm writing it out while it's fresh in mind. Just because you're using a web component doesn't mean the styles of it are entirely isolated. You might have content within a web component that is styled normally along with the rest of your website. Like this:

See the Pen Web Component with Global Styles (because no Shadow DOM) by Chris Coyier (@chriscoyier) on CodePen.

That <whats-up> element isolated the JavaScript-powered functionality of itself by attaching a click handler to the <button> inside of it. But the styling of that button comes from global CSS applied to that page.

Moving the template inside the web component

But let's say we move that <button> into the web component, so we can use <whats-up> all by itself. We could do that by .innerHTML'ing the custom element:

See the Pen Web Component with Global Styles (because no Shadow DOM) by Chris Coyier (@chriscoyier) on CodePen.

Again, entirely styled by the global CSS. Cool. That may be desirable. It also might not be desirable. Perhaps you're looking to web components to isolate styles for you.

Shadow DOMing the template

Web components can isolate styles (and abstract away HTML implementation) via the Shadow DOM. Here's that same component, using Shadow DOM instead:

See the Pen Web Component with Local Styles by Chris Coyier (@chriscoyier) on CodePen.

Note that the functionality still works (although we had to querySelector through the shadowRoot), but we've totally lost the global styling. The Shadow DOM boundary (shadow root) prevents styling coming in or going out (sorta like an iframe).

Shadow Root

There is no global way to penetrate that boundary that I'm aware of, so if you want to bring styles in, you gotta bring them into the template.

Move the styles (inline) inside the web component

See the Pen Web Component with Local Styles by Chris Coyier (@chriscoyier) on CodePen.

This would be highly obnoxious if you both really wanted to use the Shadow DOM but also wanted your global styles. It's funny that there is a Shadow DOM "mode" for open and closed for allowing or disallowing JavaScript in and out, but not CSS.

If that's you, you'll probably need to @import whatever global stylesheets you can to bring in those global styles and hope they are cached and the browser is smart about it in such a way that it isn't a big performance hit.

Link to external styles instead

I'll use CodePen's direct link to CSS feature to import the styles from the Pen itself into the web component:

See the Pen Web Component with Local Styles by Chris Coyier (@chriscoyier) on CodePen.

Apparently, there is no way to avoid somewhat of a Flash-Of-Unstyled-Component this way though, so inlining styles is recommended until there is.

Custom properties go through the shadow DOM

Another important thing to know is that CSS custom properties penetrate the Shadow DOM! That's right, they do. You can select the web component in the CSS and set them there:

See the Pen Web Component with Custom Properties by Chris Coyier (@chriscoyier) on CodePen.

HTML you point to via a slot is globally stylable

So if you have like:

<my-module> <h2 slot="header">My Module</h2> </my-module>

And where you define your shadow DOM you use that header:

<div class="module"> <slot name="header"></slot> </div>

Then the <h2> will be globally stylable, but that <div class="module"> will not.

See the Pen Slots and styling web components by Chris Coyier (@chriscoyier) on CodePen.

::part and ::theme

I didn't investigate this too much because this is a spec that's still being worked on I guess, but will likely ultimately play a large role here. Monica Dinculescu covers it in detail in her article ::part and ::theme, an ::explainer.

Looks like a way to reach into the shadow DOM, but only to the exact level that matches, no deeper.


#shadow-root

...

...

/* not styleable
x-foo::part(some-box) { ... } /* nope */ x-foo::part(some-box) span { ... }

The post Styling a Web Component appeared first on CSS-Tricks.

Syndicate content
©2003 - Present Akamai Design & Development.