The State of CSS Reflections

Note: For the convenience of readers, this paper adopts free translation, not literal translation; Secondly, the original text is very long, and the author also provides a browser compatibility scheme. This translation mainly introduces the effect of box reflex property, but does not elaborate on compatibility issues. Readers who are interested can click on the original text to read

Star: //github.com/qiud…

I recently found a pure CSS implementation of a fence animation with gradient reflection and 3D rotation on CodePen by using 10

elements to create 10 bars, then copying the entire

element and creating a gradient mask to create a gradient effect as the reflection of the fence.

It sounds a bit like scratching the back of your right ear with the toes of your left foot! Not to mention that this gradient mask approach doesn’t work on backgrounds that aren’t monochromatic. Isn’t there a better csS-BASED approach?

The answer is yes, there are better ways to do this! Unfortunately, this approach is not compatible enough, and if we don’t want to use canvas, but want to be compatible with all major browsers, this is definitely the best approach.

This article will explore all the options available to us for creating reflections today, explain the “similar” solutions, the pain they cause with cross-browser issues, and finally discuss my thoughts on what to do about them.

Based on the code

Before discussing reflections, let’s look at how to create, position, and color a fence bar, since this section is common to all browsers.

First we create a.loader wrapper with 10.bar elements

<div class='loader'>
  <div class='bar'></div>
  <! Create 9 rows -->
</div>
Copy the code

Instead of writing HTML directly, we can also use a preprocessor, such as Haml:

.loader
  - 10.times do
  .bar
Copy the code

We position these elements from the middle of the viewport. Usually we use top: 50% to center it, but it’s more convenient if we use bottom: 50%.

div {
  position: absolute;
  bottom: 50%; left: 50%;
}
Copy the code

We define the width and height of the bars and give it a background:

$bar-w: 1.25 em;
$bar-h: 5 * $bar-w;

.bar {
  width: $bar-w; height: $bar-h;
  background: currentColor;
}
Copy the code

We want the bottom edge of the grating to overlap with the horizontal axis of the viewport. This is achieved by setting bottom: 50%.

At this point, all of our bars are stacked together, their left edge overlaps with the vertical axis of the viewport, and the bottom edge overlaps with the horizontal axis.

Locate the fence

We need to position these fences so that the left edge of the first bar and the right edge of the last bar are the same distance from the longitudinal central axis. This distance is always equal to half of the number of bars ($n) multiplied by the width of the bars ($bar-w). The initial demo used plain CSS. Now we’re using Sass to simplify the amount of code. See here:

Translator: Special reminder, bar wide$bar-wIn the middle isconnector, not a minus sign, to avoid ambiguity, subsequent use$bar_walternative

This means that we need to move the first grid to the left by 0.5 * $n * $bar_w, starting with all the grid positions. The left is the negative direction of the x axis, which means that a negative sign is required in front of it, so the margin-left value of the first bar is -.5 * $n * $bar_w.

Margin-left = -.5 * $n * $bar_w + $bar_w; margin-left = -.

Margin-left = -.5 * $n * $bar_w + 2 * $bar_w;

Margin-left: -.5 * $n * $bar-w + ($n – 1) * $bar-w

Picture below: View online

$n: 10;

@for $i from 0 to $n {
  .bar:nth-child(#{$i+ {1})margin-left: ($i-.5 * $n) * $bar-w; }}Copy the code

We give them box-shadow so that we can clearly see the end of one bar and the beginning of the next:

Colour the fence

The background color of the fence transitions from dark blue at the far left (#1e3f57) to light blue at the far right (# 63a6C1), which sounds like something Sass’s Mix () function does. The first argument to mix() is light blue, the second argument is dark blue, and the third argument (called the relative weight, in %) is the amount of light blue in the final mix.

For the first grid, the amount of light blue should be 0-0%, so the end result is only dark blue;

Similarly, for the last bar, the amount of light blue should be 100%-100%, and the background color should be light blue;

For the remaining bars, we need to evenly distribute the median. Suppose we have $n bars, the first is 0%, the last is 100%, and then we need to divide them into $n minus 1 isothermal intervals. The diagram below:

$i
$i * 100% / ($n - 1)

$c: #63a6c1 #1e3f57; // 1st = light 2nd = dark

@for $i from 0 to $n {
  // list of mix() arguments for current bar
  $args: append($c.$i * 100% / ($n - 1));

  .bar:nth-child(#{$i+ {1})background: mix($args...). ; }}Copy the code

Now, the fences look exactly as they did in the original demo: viewed online

-webkit-box-reflect

Box-reflect is still a non-standard attribute and is not yet supported in many major browsers, but fortunately it works fine in WebKit browsers by adding the browser’s private attribute.

Let’s see how it works. Its value has three parts:

- its - box - reflect: none | < direction > < offset >? <mask-box-image>?Copy the code
  • <direction>: Direction of reflection, can bebelow.left.above.rightAny value in

  • : the distance between the reflection and the original image. The value can be a fixed pixel value or percentage

  • : Sets the mask effect of the reflection, which can be a background image or a gradient image


The following interactive demo illustrates this (you can click to change the reflection’s direction, offset, etc.) :

Visit demo directly from play!

In our example, the first thing that comes to mind is to add this to the.loader:

.loader {
  -webkit-box-reflect: below 0 linear-gradient(rgba(#fff.0), rgba(#fff.7));
}
Copy the code


Second, we need to set the size of the loader, because it contains the fences are absolutely positioned, the actual size of the loader is 0px X 0px. We define its width as the sum of all the fence widths and its height as the fence height:

$loader-w: $n * $bar-w;

.loader {
  width: $loader-w; height: $bar-h;
  box-shadow: 0 0 0 1px red;
}
Copy the code

After adding the above code, you should see the following in the WebKit browser: View online

We can already see the loader boundaries and some reflections, but they are no longer in the right place. We need to move the loader left to center, while having the bottom of the fence overlap the bottom of their parent element:

.loader { margin-left: -.5 * $loader-w; }

.bar { bottom: 0; }
Copy the code

In this way, the problem of positioning is solved, and the effect is as follows:

// Omit a bunch of Compatibility schemes for Firefox: Element ()+ SVG mask……

animation

The original animation implemented in CodePen was very simple, just a 3D rotating fence:

@keyframes bar {
  0% {
  transform: rotate(-.5turn) rotateX(-1turn); Were 75% and 100%} {transform: none; }}Copy the code

Apply animation to all fences:

animation: bar 3s cubic-bezier(.81.04.4.7) infinite;
Copy the code

Then add a different delay for each bar:

animation-delay: $i*50ms;
Copy the code

Since we are a 3D rotating fence, we also need to add the Perspective attribute on the Loader element:.

.loader {
  perspective: 62.5 em;
}
Copy the code

But this only works if -webkit-box-reflect is used in the WebKit browser. The final result: online view

One final thought

Forget it, this paragraph need not translate!

-webkit-box-reflect and Element ()+mask (SVG’s mask tag) solve the problem of cross-browser reflection, but box-Reflect is a great solution