Tag: Line

10 modern layouts in 1 line of CSS

, ,

When a Line Doesn’t Break

We expect a line to break when the text on that line reaches the parent box boundaries. We see this every time we create a paragraph, just like this one. When the parent box doesn’t have enough room for the next word in a line, it breaks it and moves down to the next line and repeats that process.

Well, that’s how it works when words are separated by spaces or other whitespace. As far as CSS goes, there are five (!) properties that can possibly affect how and when a line breaks. Let’s not get into all that again though. Let’s instead look at a situation where it seems like a line is going to break but it doesn’t as an excuse to learn a little something about line breaking.

What happens in a situation where there is no way for the browser to know when it’s OK to make a break?

Let’s get ourselves into a bad situation with a “tag list” and then get ourselves out of it. Here’s our markup:

<ul>   <li>PHP</li>   <li>JavaScript</li>   <li>Rust</li>   <!-- etc. --> </ul>

Next, we’ll apply CSS that overwrites the default list style from its default vertical orientation to horizontal by making the list items display inline.

ul {   margin: 0;   padding: 0;   list-style: none; } 
 li {   display: inline;   padding-right: 5px; }

The list looks just like we want it. I added a little bit of space between one list item and another, so it doesn’t look too crowded.

Now let’s introduce a wrapper element to the mix. That’s essentially a div around the unordered list. We can give it a class, say, .tags.

<div class="tags">   <ul>     <li>PHP</li>     <li>JavaScript</li>     <li>Rust</li>   </ul> </div>

Let’s say we want to give the wrapper a fixed width of 200px. That’s where we should expect to see line breaks happen as the unordered list bumps into wrapper’s boundaries.

.tags {   width: 200px; }

Here comes the interesting part. Many people use minifiers in their build process to reduce file sizes by getting rid of unnecessary values. Some of these values are whitespaces, which includes spaces, tabs, and line breaks (such as carriage return and line feed) characters that are use for formatting purposes but considered by minifies to be irrelevant to the final result.

If we “minify” our HTML by removing new lines, then this is what we get:

<div class="tags"><ul><li>PHP</li><li>JavaScript</li><li>Rust</li></ul></div>

UH OH. As you can see, the list is not breaking at the 200px boundary anymore. Why? What is different now? Personally, I thought HTML didn’t care about whitespaces. What is so different about the minified version from the original markup?

The browser does actually care about whitespaces… but only sometimes. And this just so happens to be one of those instances. When the page is being parsed, the parser sees this list as one long word because, from its perspective, there are no characters that differentiate one from the other.

One might think having the padding is affecting things. But if we remove the padding from our list items we still get the same result… only with no spacing between items.

The browser sees the entire list as a single word.

We can get natural line breaks from special characters

Besides spaces, excluding non-breaking spaces (&nbsp;), there are some other characters that will force a line break, including:

  • After hypen (‐)
  • After en dash ()
  • Before and after em dash ()
  • After question mark (?)
  • Zero-width white space (U+200B or &#8203;)

These line breaks happen at rendering time which means the browser still sees this as one long word. Adding a new list item to the tag list with any of these characters will force a line break. Let’s add “Objective-C” to the list. Its name contains a hyphen, which we can use to see how it affects things.

For better readability purpose the code code will have indentation and new line.

<div class="tags">   <ul>     <li>PHP</li>     <li>JavaScript</li>     <li>Rust</li>     <li>Objective-C</li>   </ul> </div>

That’s good. Let’s look at three solutions to our non-line-breaking list along these lines.

Solution 1: Add one of the breaking characters

We can keep forcing line breaks with those breaking characters like we just did. But remember, if you are using a minifier, adding the spaces in or after the closing tag won’t guaranteed it won’t be removed, as not all minifiers work the same way.

<div class="tags">   <ul>     <li>PHP </li>     <li>JavaScript </li>     <li>Rust </li>     <li>Objective-C </li>   </ul> </div>

Solution 2: Use pseudo-elements

The breaking character can also be added using the ::before and ::after pseudo-elements in CSS. What makes this solution effective is that it’s not affected by an HTML minifier because the whitespace is added when the CSS is applied.

But, before we move on, let’s talk a moment about collapsing whitespaces.

The browser collapses whitespace before and after a character that forces a line break inside inline elements. With this in mind, there’s a little trick to using ::after and the content property with whitespacing and display: inline-block. The inline-block element adds a breaking character at the end of the text. Then the content property space comes after the breaking character created by the inline-block element, which results in the space being removed at rendering time. That is, unless the white-space property is set to pre.

Solution 3: Use inline-block instead

Perhaps you have bumped into a fight with space between inline-block elements in CSS before. We can use the inline-block value on the display  property to force a line break because the inline-block element already has the extra whitespace we need. This works similar to adding a zero-width space character, but the list items will have no visual separation.

Solution 4: Use flex or inline-flex

Another solution is to define the unordered list as a flex container, which allows us to use flex-flow to set the direction of the list and to make sure it to multiple lines when needed.

We can also turn to the display: inline-flex instead of inline-block solution. The idea here is that the entire flexible container display inline.


So, we started this post with a situation that might come up when using a minifier. That said, minifiers — and many libraries for that matter — are smart and will try to prevent this line-breaking issue from happening.

Sure, it’s not an extremely common situation to bump into. It’s really one of those things that can fly under the radar if we’re not paying attention but, if it does happen, at least we know there are ways to work around it.

The post When a Line Doesn’t Break appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

How to Tame Line Height in CSS

In CSS, line-height is probably one of the most misunderstood, yet commonly-used attributes. As designers and developers, when we think about line-height, we might think about the concept of leading from print design — a term, interestingly enough, that comes from literally putting pieces of lead between lines of type. 

Leading and line-height, however similar, have some important differences. To understand those differences, we first have to understand a bit more about typography. 

An overview of typography terms

In traditional Western type design, a line of text is comprised of several parts: 

  • Baseline: This is the imaginary line on which the type sits. When you write in a ruled notebook, the baseline is the line on which you write.
  • Descender: This line sits just below the baseline. It is the line that some characters — like lowercase g, j, q, y and p  — touch below the baseline. 
  • X-height: This is (unsurprisingly) the height of a normal, lowercase x in a line of text. Generally, this is the height of other lowercase letters, although some may have parts of their characters that will exceed the x-height. For all intents and purposes, it servers as the perceived height of lowercase letters.
  • Cap-height: This is the height of most capital letters on a given line of text.
  • Ascender: A line that oftentimes appears just above the cap height where some characters like a lowercase h or b might exceed the normal cap height.
Illustrating the ascender, cap height, x-height, baseline and descender of the Lato font with The quick fox as sample text.

Each of the parts of text described above are intrinsic to the font itself. A font is designed with each of these parts in mind; however, there are some parts of typography that are left up to the type setter (like you and me!) rather than the designer. One of these is leading.

Leading is defined as the distance between two baselines in a set of type.

Two lines of text with an order box around the second line ofd text indicating the leading.

A CSS developer might think, “OK, leading is the line-height, let’s move on.” While the two are related, they are also different in some very important ways.

Let’s take a blank document and add a classic “CSS reset” to it:

* {   margin: 0;   padding: 0; }

This removes the margin and padding from every single element.

We’ll also use Lato from Google Fonts as our font-family.

We will need some content, so let’s an create an <h1> tag with some text and set the line-height to something obnoxiously huge, like 300px. The result is a single line of text with a surprising amount of space both above and below the single line of text.

When a browser encounters the line-height property, what it actually does is take the line of text and place it in the middle of a “line box” which has a height matching the element’s line-height. Instead of setting the leading on a font, we get something akin to padding one either side of the line box.

Two lines of text with orange borders around each line of text, indicating the line box for each line. The bottom border of the first line and the top border of the second line are touching.

As illustrated above, the line box wraps around a line of text where leading is created by using space below one line of text and above the next. This means that for every text element on a page there will be half of the leading above the first line of text and after the last line of text in a particular text block.

What might be more surprising is that explicitly setting the line-height and font-size on an element with the same value will leave extra room above and below the text. We can see this by adding a background color to our elements.

This is because even though the font-size is set to 32px, the actual text size is something less than that value because of the generated spacing.

Getting CSS to treat line-height like leading

If we want CSS to use a more traditional type setting style instead of the line box, we’ll want a single line of text to have no space either above or below it — but allow for multi-line elements to maintain their entire line-height value. 

It is possible to teach CSS about leading with a little bit of effort. Michael Taranto released a tool called Basekick that solves this very issue. It does so by applying a negative top margin to the ::before pseudo-elementand a translateY to the element itself. The end result is a line of text without any extra space around it.

The most up-to-date version of Basekick’s formula can be found in the source code for the Braid Design System from SEEK. In the example below, we are writing a Sass mixin to do the heavy lifting for us, but the same formula can be used with JavaScript, Less, PostCSS mixins, or anything else that provides these kinds of math features.

@function calculateTypeOffset($ lh, $ fontSize, $ descenderHeightScale) {   $ lineHeightScale: $ lh / $ fontSize;   @return ($ lineHeightScale - 1) / 2 + $ descenderHeightScale; } 
 @mixin basekick($ typeSizeModifier, $ baseFontSize, $ descenderHeightScale, $ typeRowSpan, $ gridRowHeight, $ capHeight) {   $ fontSize: $ typeSizeModifier * $ baseFontSize;   $ lineHeight: $ typeRowSpan * $ gridRowHeight;   $ typeOffset: calculateTypeOffset($ lineHeight, $ fontSize, $ descenderHeightScale);   $ topSpace: $ lineHeight - $ capHeight * $ fontSize;   $ heightCorrection: 0;      @if $ topSpace > $ gridRowHeight {     $ heightCorrection: $ topSpace - ($ topSpace % $ gridRowHeight);   }      $ preventCollapse: 1;      font-size: #{$ fontSize}px;   line-height: #{$ lineHeight}px;   transform: translateY(#{$ typeOffset}em);   padding-top: $ preventCollapse; 
   &::before {     content: "";     margin-top: #{-($ heightCorrection + $ preventCollapse)}px;     display: block;     height: 0;   } }

At first glance, this code definitely looks like a lot of magic numbers cobbled together. But it can be broken down considerably by thinking of it in the context of a particular system. Let’s take a look at what we need to know:

  • $ baseFontSize:This is the normal font-size for our system around which everything else will be managed. We’ll use 16px as the default value.
  • $ typeSizeModifier: This is a multiplier that is used in conjunction with the base font size to determine the font-size rule. For example, a value of 2 coupled with our base font size of 16px will give us font-size: 32px.
  • $ descenderHeightScale: This is the height of the font’s descender expressed as a ratio. For Lato, this seems to be around 0.11.
  • $ capHeight: This is the font’s specific cap height expressed as a ratio. For Lato, this is around 0.75.
  • $ gridRowHeight: Layouts generally rely on default a vertical rhythm to make a nice and consistently spaced reading experience. For example, all elements on a page might be spaced apart in multiples of four or five pixels. We’ll be using 4 as the value because it divides easily into our $ baseFontSize of 16px.
  • $ typeRowSpan: Like $ typeSizeModifier, this variable serves as a multiplier to be used with the grid row height to determine the rule’s line-height value. If our default grid row height is 4 and our type row span is 8, that would leave us with line-height: 32px.

Now we can then plug those numbers into the Basekick formula above (with the help of SCSS functions and mixins) and that will give us the result below.

That’s just what we’re looking for. For any set of text block elements without margins, the two elements should bump against each other. This way, any margins set between the two elements will be pixel perfect because they won’t be fighting with the line box spacing.

Refining our code

Instead of dumping all of our code into a single SCSS mixin, let’s organize it a bit better. If we’re thinking in terms of systems, will notice that there are three types of variables we are working with:

Variable Type Description Mixin Variables
System Level These values are properties of the design system we’re working with. $ baseFontSize
$ gridRowHeight
Font Level These values are intrinsic to the font we’re using. There might be some guessing and tweaking involved to get the perfect numbers. $ descenderHeightScale
$ capHeight
Rule Level These values will are specific to the CSS rule we’re creating $ typeSizeMultiplier
$ typeRowSpan

Thinking in these terms will help us scale our system much easier. Let’s take each group in turn.

First off, the system level variables can be set globally as those are unlikely to change during the course of our project. That reduces the number of variables in our main mixin to four:

$ baseFontSize: 16; $ gridRowHeight: 4;  @mixin basekick($ typeSizeModifier, $ typeRowSpan, $ descenderHeightScale, $ capHeight) {   /* Same as above */ }

We also know that the font level variables are specific to their given font family. That means it would be easy enough to create a higher-order mixin that sets those as constants:

@mixin Lato($ typeSizeModifier, $ typeRowSpan) {   $ latoDescenderHeightScale: 0.11;   $ latoCapHeight: 0.75;      @include basekick($ typeSizeModifier, $ typeRowSpan, $ latoDescenderHeightScale, $ latoCapHeight);   font-family: Lato; }

Now, on a rule basis, we can call the Lato mixin with little fuss:

.heading--medium {   @include Lato(2, 10); }

That output gives us a rule that uses the Lato font with a font-size of 32px and a line-height of 40px with all of the relevant translates and margins. This allows us to write simple style rules and utilize the grid consistency that designers are accustomed to when using tools like Sketch and Figma.

As a result, we can easily create pixel-perfect designs with little fuss. See how well the example aligns to our base 4px grid below. (You’ll likely have to zoom in to see the grid.)

Doing this gives us a unique superpower when it comes to creating layouts on our websites: We can, for the first time in history, actually create pixel-perfect pages. Couple this technique with some basic layout components and we can begin creating pages in the same way we would in a design tool.

Moving toward a standard

While teaching CSS to behave more like our design tools does take a little effort, there is potentially good news on the horizon. An addition to the CSS specification has been proposed to toggle this behavior natively. The proposal, as it stands now, would add an additional property to text elements similar to line-height-trim or leading-trim

One of the amazing things about web languages is that we all have an ability to participate. If this seems like a feature you would like to see as part of CSS, you have the ability to drop in and add a comment to that thread to let your voice be heard.

The post How to Tame Line Height in CSS appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

How to Make a Line Chart With CSS

Line,  bar, and pie charts are the bread and butter of dashboards and are the basic components of any data visualization toolkit. Sure, you can use SVG or a JavaScript chart library like Chart.js or a complex tool like D3 to create those charts, but what if you don’t want to load yet another library into your already performance-challenged website?

There are plenty of articles out there for creating CSS-only bar charts, column charts, and pie charts, but if you just want a basic line chart, you’re out of luck. While CSS can “draw lines” with borders and the like, there is no clear method for drawing a line from one point to another on an X and Y coordinate plane. 

Well, there is a way! If all you need is a simple line chart, there’s no need to load in a huge JavaScript library or even reach for SVG. You can make everything you need with just CSS and a couple of custom properties in your HTML. Word of warning, though. It does involve a bit of trigonometry. If that didn’t scare you off, then roll up your shirt sleeves, and let’s get started!

Here’s a peek at where we’re headed:

Let’s start with the baseline

If you are creating a line chart by hand (as in, literally drawing lines on a piece of graph paper), you would start by creating the points, then connecting those points to make the lines. If you break the process down like that, you can recreate any basic line chart in CSS. 

Let’s say we have an array of data to display points on an X and Y coordinate system, where days of the week fall along the X-axis and the numeric values represent points on the Y-axis.

[   { value: 25, dimension: "Monday" },   { value: 60, dimension: "Tuesday" },   { value: 45, dimension: "Wednesday" },   { value: 50, dimension: "Thursday" },   { value: 40, dimension: "Friday" } ]

Let’s create an unordered list to hold our data points and apply some styles to it. Here’s our HTML:

<figure class="css-chart" style="--widget-size: 200px;">   <ul class="line-chart">     <li>       <div class="data-point" data-value="25"></div>     </li>     <li>       <div class="data-point" data-value="60"></div>     </li>     <li>       <div class="data-point" data-value="45"></div>     </li>     <li>       <div class="data-point" data-value="50"></div>     </li>     <li>       <div class="data-point" data-value="40"></div>     </li>   </ul> </figure>

A couple notes to glean here. First is that we’re wrapping everything in a <figure> element, which is a nice semantic HTML way of saying this is self-contained content, which also provides us the optional benefit of using a <figcaption>, should we need it. Secondly, notice that we’re storing the values in a data attribute we’re calling data-value that’s contained in its own div inside a list item in the unordered list. Why are we using a separate div instead of putting the class and attribute on the list items themselves? It’ll help us later when we get to drawing lines.

Lastly, note that we have an inlined custom property on the parent <figure>  element that we’re calling --widget-size. We’ll use that in the CSS, which is going to look like this:

/* The parent element */ .css-chart {   /* The chart borders */   border-bottom: 1px solid;   border-left: 1px solid;   /* The height, which is initially defined in the HTML */   height: var(--widget-size);   /* A little breathing room should there be others items around the chart */   margin: 1em;   /* Remove any padding so we have as much space to work with inside the element */   padding: 0;   position: relative;   /* The chart width, as defined in the HTML */   width: var(--widget-size); } 
 /* The unordered list holding the data points, no list styling and no spacing */ .line-chart {   list-style: none;   margin: 0;   padding: 0; } 
 /* Each point on the chart, each a 12px circle with a light border */ .data-point {   background-color: white;   border: 2px solid lightblue;   border-radius: 50%;   height: 12px;   position: absolute;   width: 12px; }

The above HTML and CSS will give us this not-so-exciting starting point:

Well, it sort of looks like a chart.

Rendering data points

That doesn’t look like much yet. We need a way to draw each data point at its respective X and Y coordinate on our soon-to-be chart. In our CSS, we’ve set the .data-point class to use absolute positioning and we set a fixed width and height on its parent .css-chart container with a custom property. We can use that to calculate our X and Y positions.

Our custom property sets the chart height at 200px and, in our values array, the largest value is 60. If we set that data point as the highest point on the chart’s Y axis at 200px, then we can use the ratio of any value in our data set to 60 and multiply that by 200 to get the Y coordinate of all of our points. So our largest value of 60 will have a Y value that can be calculated like this:

(60 / 60) * 200 = 200px 

And our smallest value of 25 will end up with a Y value calculated the same way:

(25 / 60) * 200 = 83.33333333333334px

Getting the Y coordinate for each data point is easier. If we are spacing the points equally across the graph, then we can divide the width of the chart (200px) by the number of values in our data array (5) to get 40px. That means the first value will have an X coordinate of 40px (to leave a margin for a left axis if we want one), and the last value will have an X coordinate of 200px.

You just got mathed! 🤓

For now, let’s add inline styles to each of the divs in the list items. Our new HTML becomes this, where the inline styles contain the calculated positioning for each point.

<figure class="css-chart">   <ul class="line-chart">     <li>       <div class="data-point" data-value="25" style="bottom: 83.33333333333334px; left: 40px;"></div>     </li>     <li>       <div class="data-point" data-value="60" style="bottom: 200px; left: 80px;"></div>     </li>     <li>       <div class="data-point" data-value="45" style="bottom: 150px; left: 120px;"></div>     </li>     <li>       <div class="data-point" data-value="50" style="bottom: 166.66666666666669pxpx; left: 160px;"></div>     </li>     <li>       <div class="data-point" data-value="40" style="bottom: 133.33333333333331px; left: 200px;"></div>     </li>   </ul> </figure>
We have a chart!

Hey, that looks a lot better! But even though you can see where this is going, you still can’t really call this a line graph. No problem. We only need to use a little more math to finish our game of connect-the-dots. Take a look at the picture of our rendered data points again. Can you see the triangles that connect them? If not, maybe this next picture will help:

Why is that important? Shhh, the answer’s coming up next.

Rendering line segments

See the triangles now? And they’re not just any old triangles. They’re the best kind of triangles (for our purposes anyway) because they are right triangles! When we calculated the Y coordinates of our data points earlier, we were also calculating the length of one leg of our right triangle (i.e. the “run” if you think of it as a stair step). If we calculate the difference in the X coordinate from one point to the next, that will tell us the length of another side of our right triangle (i.e. the “rise” of a stair step). And with those two pieces of information, we can calculate the length of the magical hypotenuse which, as it turns out, is exactly what we need to draw to the screen in order to connect our dots and make a real line chart.

For example, let’s take the second and third points on the chart.

<!-- ... -->  
 <li>   <div class="data-point" data-value="60" style="bottom: 200px; left: 80px;"></div> </li> <li>   <div class="data-point" data-value="45" style="bottom: 150px; left: 120px;"></div> </li> 
 <!-- ... -->

The second data point has a Y value of 200 and the third data point has a Y value of 150, so the opposite side of the triangle connecting them has a length of 200 minus 150, or 50. It has an adjacent side that is 40 pixels long (the amount of spacing we put between each of our points).

That means the length of the hypotenuse is the square root of 50 squared plus 40 squared, or 64.03124237432849.

The hypotenuse is what we need to draw our line graph

Let’s create another div inside of each list item in the chart that will serve as the hypotenuse of a triangle drawn from that point. Then we’ll set an inline custom property on our new div that contains the length of that hypotenuse.

<!-- ... --> 
 <li>   <div class="data-point" data-value="60"></div>   <div class="line-segment" style="--hypotenuse: 64.03124237432849;"></div> </li> 
 <!-- ... -->

While we’re at it, our line segments are going to need to know their proper X and Y coordinates, so let’s remove the inline styles from our .data-point elements and add CSS custom properties to their parent (the <li> element) instead. Let’s call these properties, creatively, --x and --y. Our data points don’t need to know about the hypotenuse (the length of our line segment), so we can add a CSS custom property for the length of the hypotenuse directly to our .line-segment. So now our HTML will look like this:

<!-- ... --> 
 <li style="--y: 200px; --x: 80px">   <div class="data-point" data-value="60"></div>   <div class="line-segment" style="--hypotenuse: 64.03124237432849;"></div> </li> 
 <!-- ... -->

We’ll need to update our CSS to position the data points with those new custom properties and style up the new .line-segment div we added to the markup:

.data-point {   /* Same as before */ 
   bottom: var(--y);   left: var(--x); } 
 .line-segment {   background-color: blue;   bottom: var(--y);   height: 3px;   left: var(--x);   position: absolute;   width: calc(var(--hypotenuse) * 1px); }
We have all of the parts now. They just don’t fit together correctly yet.

Well, we have line segments now but this isn’t at all what we want. To get a functional line chart, we need to apply a transformation. But first, let’s fix a couple of things.

First off, our line segments line up with the bottom of our data points, but we want the origin of the line segments to be the center of the data point circles. We can fix that with a quick CSS change to our .data-point styles. We need to adjust their X and Y position to account for both the size of the data point and its border as well as the width of the line segment.

.data-point {   /* ... */ 
   /* The data points have a radius of 8px and the line segment has a width of 3px,      so we split the difference to center the data points on the line segment origins */   bottom: calc(var(--y) - 6.5px);   left: calc(var(--x) - 9.5px); }

Secondly, our line segments are being rendered on top of the data points instead of behind them. We can address that by putting the line segment first in our HTML:

<!-- ... --> 
 <li style="--y: 200px; --x: 80px">     <div class="line-segment" style="--hypotenuse: 64.03124237432849;"></div>     <div class="data-point" data-value="60"></div> </li> 
 <!-- ... -->

Applying transforms, FTW

We’ve almost got it now. We just need to do one last bit of math. Specifically, we need to find the measure of the angle that faces the opposite side of our right triangle and then rotate our line segment by that same number of degrees.

How do we do that? Trigonometry! You may recall the little mnemonic trick to remember how sine, cosine and tangent are calculated:

  • SOH (Sine = Opposite over Hypotenuse
  • CAH (Cosine = Adjacent over Hypotenuse)
  • TOA (Tangent = Opposite over Adjacent)

You can use any of them because we know the length of all three sides of our right triangle. I picked sine, so that that leaves us with this equation:

sin(x) = Opposite / Hypotenuse

The answer to that equation will tell us how to rotate each line segment to have it connect to the next data point. We can quickly do this in JavaScript using Math.asin(Opposite / Hypotenuse). It will give us the answer in radians though, so we’ll need to multiply the result by (180 / Math.PI).

Using the example of our second data point from earlier, we already worked out that the opposite side has a length of 50 and the hypotenuse has a length of 64.03124237432849, so we can re-write our equation like this:

sin(x) = 50 /  64.03124237432849 = 51.34019174590991

That’s the angle we’re looking for! We need to solve that equation for each of our data points and then pass the value as a CSS custom property on our .line-segment elements. That will give us HTML that looks like this:

<!-- ... --> 
 <li style="--y: 200px; --x: 80px">   <div class="data-point" data-value="60"></div>   <div class="line-segment" style="--hypotenuse: 64.03124237432849; --angle: 51.34019174590991;"></div> </li> 
 <!-- ... -->

And here’s where we can apply those properties in the CSS:

.line-segment {   /* ... */   transform: rotate(calc(var(--angle) * 1deg));   width: calc(var(--hypotenuse) * 1px); }

Now when we render that, we have our line segments!

Well, the line segments are definitely rotated. We need one more step.

Wait, what? Our line segments are all over the place. What now? Oh, right. By default, transform: rotate() rotates around the center of the transformed element. We want the rotation to occur from the bottom-left corner to angle away from our current data point to the next one. That means we need to set one more CSS property on our .line-segment class.

.line-segment {   /* ... */   transform: rotate(calc(var(--angle) * 1deg));   transform-origin: left bottom;   width: calc(var(--hypotenuse) * 1px); }

And, now when we render it, we finally get the CSS-only line graph we’ve been waiting for.

At last! A line graph!

Important note: When you calculate the value of the opposite side (the “rise”), make sure it’s calculated as the “Y position of the current data point” minus the “Y position of the next data point.” That will result in a negative value when the next data point is a larger value (higher up on the graph) than the current data point which will result in a negative rotation. That’s how we ensure the line slopes upwards.

When to use this kind of chart

This approach is great for a simple static site or for a dynamic site that uses server-side generated content. Of course, it can also be used on a site with client-side dynamically generated content, but then you are back to running JavaScript on the client. The CodePen at the top of this post shows an example of client-side dynamic generation of this line chart.

The CSS calc() function is highly useful, but it can’t calculate sine, cosine, and tangent for us. That means you’d have to either calculate your values by hand or write a quick function (client-side or server-side) to generate the needed values (X, Y, hypotenuse and angle) for our CSS custom properties.

I know some of you got through this and will feel like it’s not vanilla CSS if it requires a script to calculate the values — and that’s fair. The point is that all of the chart rendering is done in CSS. The data points and the lines that connect them are all done with HTML elements and CSS that works beautifully, even in a statically rendered environment with no JavaScript enabled. And perhaps more importantly, there’s no need to download yet another bloated library just to render a simple line graph on your page.

Potential improvements

As with anything, there’s always something we can do to take things to the next level. In this case, I think there are three areas where this approach could be improved.

Responsiveness

The approach I’ve outlined uses a fixed size for the chart dimensions, which is exactly what we don’t want in a responsive design. We can work around this limitation if we can run JavaScript on the client. Instead of hard-coding our chart size, we can set a CSS custom property (remember our --widget-size property?), base all of the calculations on it, and update that property when the container or window either initially displays or resizes using some form of a container query or a window resize listener.

Tooltips

We could add a ::before pseudo-element to  .data-point to display the data-value information it contains in a tooltip on hover over the data point. This is a nice-to-have sort of touch that helps turn our simple chart into a finished product.

Axis lines

Notice that the chart axises are unlabeled? We could distribute labels representing the highest value, zero, and any number of points between them on the axis.

Margins

I tried to keep the numbers as simple as possible for this article, but in the real world, you would probably want to include some margins in the chart so that data points don’t overlap the extreme edges of their container. That could be as simple as subtracting the width of a data point from the range of your y coordinates. For the X coordinates, you could similarly remove the width of a data point from the total width of the chart before dividing it up into equal regions.


And there you have it! We just took a good look at an approach to charting in CSS, and we didn’t even need a library or some other third-party dependency to make it work. 💥

The post How to Make a Line Chart With CSS appeared first on CSS-Tricks.

CSS-Tricks

,
[Top]

Weaving a Line Through Text in CSS

Earlier this year, I came across this demo by Florin Pop, which makes a line go either over or under the letters of a single line heading. I thought this was a cool idea, but there were a few little things about the implementation I felt I could simplify and improve at the same time.

First off, the original demo duplicates the headline text, which I knew could be easily avoided. Then there’s the fact that the length of the line going through the text is a magic number, which is not a very flexible approach. And finally, can’t we get rid of the JavaScript?

So let’s take a look into where I ended up taking this.

HTML structure

Florin puts the text into a heading element and then duplicates this heading, using Splitting.js to replace the text content of the duplicated heading with spans, each containing one letter of the original text.

Already having decided to do this without text duplication, using a library to split the text into characters and then put each into a span feels a bit like overkill, so we’re doing it all with an HTML preprocessor.

- let text = 'We Love to Play'; - let arr = text.split('');  h1(role='image' aria-label=text)   - arr.forEach(letter => {     span.letter #{letter}   - });

Since splitting text into multiple elements may not work nicely with screen readers, we’ve given the whole thing a role of image and an aria-label.

This generates the following HTML:

<h1 role="image" aria-label="We Love to Play">   <span class="letter">W</span>   <span class="letter">e</span>   <span class="letter"> </span>   <span class="letter">L</span>   <span class="letter">o</span>   <span class="letter">v</span>   <span class="letter">e</span>   <span class="letter"> </span>   <span class="letter">t</span>   <span class="letter">o</span>   <span class="letter"> </span>   <span class="letter">P</span>   <span class="letter">l</span>   <span class="letter">a</span>   <span class="letter">y</span> </h1>

Basic styles

We place the heading in the middle of its parent (the body in this case) by using a grid layout:

body {   display: grid;   place-content: center; }
Screenshot of grid layout lines around the centrally placed heading when inspecting it with Firefox DevTools.
The heading doesn’t stretch across its parent to cover its entire width, but is instead placed in the middle.

We may also add some prettifying touches, like a nice font or a background on the container.

Next, we create the line with an absolutely positioned ::after pseudo-element of thickness (height) $ h:

$ h: .125em; $ r: .5*$ h;  h1 {   position: relative;      &::after {     position: absolute;     top: calc(50% - #{$ r}); right: 0;     height: $ h;     border-radius: 0 $ r $ r 0;     background: crimson;   } }

The above code takes care of the positioning and height of the pseudo-element, but what about the width? How do we make it stretch from the left edge of the viewport to the right edge of the heading text?

Line length

Well, since we have a grid layout where the heading is middle-aligned horizontally, this means that the vertical midline of the viewport coincides with that of the heading, splitting both into two equal-width halves:

SVG illustration. Shows how the vertical midline of the viewport coincides with that of the heading and splits both into equal width halves.
The middle-aligned heading.

Consequently, the distance between the left edge of the viewport and the right edge of the heading is half the viewport width (50vw) plus half the heading width, which can be expressed as a % value when used in the computation of its pseudo-element’s width.

So the width of our ::after pseudo-element is:

width: calc(50vw + 50%);

Making the line go over and under

So far, the result is just a crimson line crossing some black text:

What we want is for some of the letters to show up on top of the line. In order to get this effect, we give them (or we don’t give them) a class of .over at random. This means slightly altering the Pug code:

- let text = 'We Love to Play'; - let arr = text.split('');  h1(role='image' aria-label=text)   - arr.forEach(letter => {     span.letter(class=Math.random() > .5 ? 'over' : null) #{letter}   - });

We then relatively position the letters with a class of .over and give them a positive z-index.

.over {   position: relative;   z-index: 1; }

My initial idea involved using translatez(1px) instead of z-index: 1, but then it hit me that using z-index has both better browser support and involves less effort.

The line passes over some letters, but underneath others:

Animate it!

Now that we got over the tricky part, we can also add in an animation to make the line enter in. This means having the crimson line shift to the left (in the negative direction of the x-axis, so the sign will be minus) by its full width (100%) at the beginning, only to then allow it to go back to its normal position.

@keyframes slide { 0% { transform: translate(-100%); } }

I opted to have a bit of time to breathe before the start of the animation. This meant adding in the 1s delay which, in turn, meant adding the backwards keyword for the animation-fill-mode, so that the line would stay in the state specified by the 0% keyframe before the start of the animation:

animation: slide 2s ease-out 1s backwards;

A 3D touch

Doing this gave me another idea, which was to make the line go through every single letter, that is, start above the letter, go through it and finish underneath (or the other way around).

This requires real 3D and a few small tweaks.

First off, we set transform-style to preserve-3d on the heading since we want all its children (and pseudo-elements) to a be part of the same 3D assembly, which will make them be ordered and intersect according to how they’re positioned in 3D.

Next, we want to rotate each letter around its y-axis, with the direction of rotation depending on the presence of the randomly assigned class (whose name we change to .rev from “reverse” as “over” isn’t really suggestive of what we’re doing here anymore).

However, before we do this, we need to remember our span elements are still inline ones at this point and setting a transform on an inline element has absolutely no effect.

To get around this issue, we set display: flex on the heading. However, this creates a new issue and that’s the fact that span elements that contain only a space (" ") get squished to zero width.

Screenshot showing how the span containing only a space gets squished to zero width when setting `display: flex` on its parent.
Inspecting a space only <span> in Firefox DevTools.

A simple fix for this is to set white-space: pre on our .letter spans.

Once we’ve done this, we can rotate our spans by an angle $ a… in one direction or the other!

$ a: 2deg;  .letter {   white-space: pre;   transform: rotatey($ a); }  .rev { transform: rotatey(-$ a); }

Since rotation around the y-axis squishes our letters horizontally, we can scale them along the x-axis by a factor ($ f) that’s the inverse of the cosine of $ a.

$ a: 2deg; $ f: 1/cos($ a)  .letter {   white-space: pre;   transform: rotatey($ a) scalex($ f) }  .rev { transform: rotatey(-$ a) scalex($ f) }

If you wish to understand the why behind using this particular scaling factor, you can check out this older article where I explain it all in detail.

And that’s it! We now have the 3D result we’ve been after! Do note however that the font used here was chosen so that our result looks good and another font may not work as well.

The post Weaving a Line Through Text in CSS appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]