3. Crazy geek


The original:
https://www.smashingmagazine….


This article first send WeChat messages public number: jingchengyideng welcome attention, every day to push you fresh front-end technology articles


Abstract


In this article you will learn how the Awwwards network implements animation. This article introduces the circle element in HTML5 SVG, its stroke property, and how to use CSS variables and animate them using Vanilla JavaScript.

SVG is an XML-based markup language for defining scaled vector graphics. It allows you to draw paths, curves, and shapes from a defined set of points in a 2D plane. You can also animate these paths by adding dynamic attributes (such as stroke, color, thickness, fill, etc.).

From April 2017, the CSS Level 3 Fill and Stroke module began to support setting SVG colors and fill patterns from external stylesheets, rather than setting attributes on each element. In this tutorial, we will use simple pure hexadecimal colors, but fill and stroke properties also support patterns, gradients, and images as values.

Pay attention to: visit
Awwwards website, you’ll need to set the browser width to 1024px or higher to see the animation better.

  • The demo link
  • The source code

File structure

Let’s start by creating a file in the terminal:

🌹 mkdir note-display nil CD note-display pan touch index.html styles.css scripts

HTML This is the initial template to connect CSS and JS files:

<html lang="en">
<head>
  <meta charset="UTF-8">

  <title>Note Display</title>

  <link rel="stylesheet" href="./styles.css">
</head>
<body>
  <script src="./scripts.js"></script>
</body>
</html>

Each note element contains a list item: li to hold circle, note value, and label.

Figure: Lists item elements and their immediate children:.circle,.percent, and.label

.circle_svg is an SVG element that contains two

elements. The first is the path to fill, and the second is used to prepare the animation.

Figure: SVG elements: SVG wrappers and circular labels

Comments are integers and decimals, so you can set them to different font sizes. Label is a simple . Put all of these elements together and it looks like this:

<li class="note-display">
  <div class="circle">
    <svg width="84" height="84" class="circle__svg">
      <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle>
      <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle>
    </svg>

    <div class="percent">
      <span class="percent__int">0.</span>
      <span class="percent__dec">00</span>
    </div>
  </div>

  <span class="label">Transparent</span>
</li>

The cx and cy attributes define the X-axis and Y-axis center points of the circle. The r property defines its radius.

You may have noticed the underline/dash pattern in the class name. This is BEM (Block Element Modifier), which stands for Block, Element and Modifier respectively. It is a way to make element naming more structured, organized, and semantic.

Recommended reading: What is BEM and why is it needed

To complete the template structure, let’s wrap the four list items in an unordered list element:

Figure: The unordered list wrapper has four li child elements

<ul class="display-container"> <li class="note-display"> <div class="circle"> <svg width="84" height="84" class="circle__svg"> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle> </svg> <div class="percent"> <span class="percent__int">0.</span> <span class="percent__dec">00</span> </div> </div> <span class="label">Transparent</span>  </li> <li class="note-display"> <div class="circle"> <svg width="84" height="84" class="circle__svg"> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle> </svg> <div class="percent"> <span class="percent__int">0.</span> <span class="percent__dec">00</span> </div> </div> <span class="label">Reasonable</span> </li> <li class="note-display"> <div class="circle"> <svg width="84" height="84" class="circle__svg"> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle> </svg> <div class="percent"> <span class="percent__int">0.</span> <span class="percent__dec">00</span> </div> </div> <span class="label">Usable</span> </li> <li class="note-display"> <div class="circle"> <svg width="84" height="84" class="circle__svg"> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle> </svg> <div class="percent"> <span class="percent__int">0.</span> <span class="percent__dec">00</span> </div> </div> <span class="label">Exemplary</span> </li> </ul>

You must first ask yourself what the Transparent, Reasonable, Usable, and Exemplary tags mean. As you get more familiar with programming, you’ll discover that writing code isn’t just about making it work, it’s also about making sure it’s maintained and extended over the long term. This is only possible if your code can be easily modified.

“Acronym
TRUEIt should help you determine if the code you’re writing is going to be able to adapt to future changes.”

So, next time, ask yourself:

Transparency: Are the consequences of code changes clear? Reasonable: Is the cost-effectiveness worth it? Availability: Can I reuse it in an unexpected situation? Example: Does it use high quality as an example of future code?

  • TransparentAre the consequences of code changes clear?
  • A: Reasonable.: Is the cost-effectiveness worth it?
  • Usable: Can I reuse it in different scenarios?
  • For Exemplary information: Can it be used as a high quality code template in the future?

Note: Sandi Metz explains TRUE and other principles and how to implement them through design patterns in her book, A Guide to Object-Oriented Design Practices: Description of the Ruby Language. If you haven’t started researching design patterns, consider putting this book on your desk.

CSS

Let’s import the font and make it work for everything:


@import url('https://fonts.googleapis.com/css?family=Nixie+One|Raleway:200');

* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
}

Box-sizing: The border-box property includes the padding and border values to the total width and height of the element, so it is easier to calculate the range of the figure.

Note:
*box-sizingPlease read the description of *
“Using CSS Box Makes You Easier”_.

body {
  height: 100vh;
  color: #fff;
  display: flex;
  background: #3E423A;
  font-family: 'Nixie One', cursive;
}

.display-container {
  margin: auto;
  display: flex;
}

You can center child elements vertically and horizontally by combining rules to show: flex in the body and margin-auto in the.display-container. The.display-container element will also act as a flex-container; In this way, its children will be placed on the same line along the principal axis.

The.note-display list item will also be a flex-container. Since many of the subitems are centered, we can do this with the justify-content and align-items properties. All flex-items will be vertically and horizontally centered. If you’re not sure what they are, check out the Alignment section in the CSS Flexbox Visualization Guide.

.note-display {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin: 0 25px;
}

Let’s apply the stroke to the circle by setting ‘stroke-width, stroke-opacity, and stroke-linecap. These rules will make the picture move. Next, we add a color for each circle:

.circle__progress { fill: none; stroke-width: 3; Stroke - opacity: 0.3; stroke-linecap: round; } .note-display:nth-child(1) .circle__progress { stroke: #AAFF00; } .note-display:nth-child(2) .circle__progress { stroke: #FF00AA; } .note-display:nth-child(3) .circle__progress { stroke: #AA00FF; } .note-display:nth-child(4) .circle__progress { stroke: #00AAFF; }

In order to locate the percentage element absolutely, you must know exactly what these concepts are. The.circle element should be a reference, so let’s add position: relative to it.

Note: For a deeper, more intuitive explanation of absolute positioning, see “Understanding CSS Positions Once and for All.”

Another way to center an element is to put top: 50%, left: 50% and transform: translate(-50%, -50%); Group together to position the center of an element at its parent center.

.circle {
  position: relative;
}

.percent {
  width: 100%;
  top: 50%;
  left: 50%;
  position: absolute;
  font-weight: bold;
  text-align: center;
  line-height: 28px;
  transform: translate(-50%, -50%);
}

.percent__int { font-size: 28px; }
.percent__dec { font-size: 12px; }

.label {
  font-family: 'Raleway', serif;
  font-size: 14px;
  text-transform: uppercase;
  margin-top: 15px;
}

So far, the template should look like this:

Figure: Finished template elements and styles

Fill in the transition

Circular animations can be created with the help of two circular SVG properties: stroke-dasharray and stroke-dashoffset.


stroke-dasharrayDefine the dotted line gap pattern in a stroke.

It may require up to four values:

When it is set to a unique integer (stroke-dasharray:10), the dash and gap have the same size; For two values (stroke-dasharray:10 5), the first is applied to the dash and the second to the gap; The third and fourth forms (stroke-dasharray:10 5 2 and stroke-dasharray:10 5 2 3) produce various styles of dashed lines and gaps.

Figure: Stroke-Dasharray property value

The image on the left shows the stroke-dasharray property set to 0 to the circumference of 238px.

The second image represents the stroke-dashoffset property, which cancels out the beginning of the dash array. It also ranges from 0 to the circumference of the circle.

Figure: Stroke-Dasharray and Stroke-DashOffset properties

To produce a fill effect, we set the stroke-dasharray to a circumference length so that all of its length fills its sprint range without any gaps. We will also cancel it out with the same value so that it can be “hidden”. The stroke-dashoffset is then updated to the corresponding description, filling in its stroke based on the transition duration.

Property updates are done in the script using CSS Variables. Let’s declare the variables and set the properties:

.circle__progress--fill {
  --initialStroke: 0;
  --transitionDuration: 0;
  stroke-opacity: 1;
  stroke-dasharray: var(--initialStroke);
  stroke-dashoffset: var(--initialStroke);
  transition: stroke-dashoffset var(--transitionDuration) ease;
}

In order to set the initial value and updates the variables, let us from the use of the document. Choose all querySelectorAll. Note – the display element. Also set TransitionDuration to 900 milliseconds.

Then, we iterate through the display array, selecting its.circle__progress. circle__progress-fill and extracting the set of r attributes in the HTML to calculate the perimeter. With this, we can set the initial –dasharray and –dashoffset values.

When the –dashoffset variable is updated by setTimeout, an animation occurs:

const displays = document.querySelectorAll('.note-display');
const transitionDuration = 900;

displays.forEach(display => {
  let progress = display.querySelector('.circle__progress--fill');
  let radius = progress.r.baseVal.value;
  let circumference = 2 * Math.PI * radius;

  progress.style.setProperty('--transitionDuration', `${transitionDuration}ms`);
  progress.style.setProperty('--initialStroke', circumference);

  setTimeout(() => progress.style.strokeDashoffset = 50, 100);
});

To overmove from the top, you must rotate the.circle__svg element:

.circle__svg {
  transform: rotate(-90deg);
}

Figure: Transformation of the Stroke property

Now, let’s calculate the value of the dashoffset relative to the note. Note values are inserted into each li item through the data-* attribute. * can be replaced with any name that fits your needs, which can then be retrieved from the element’s dataset in the metadata: element.dataset.*.

Note: You can get more information about the data-* property on MDN Web Docs.

Our property will be named “data-note” :

<ul class="display-container"> + <li class="note-display" data-note="7.50"> <div class="circle"> < SVG width="84" height="84" class="circle__svg"> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle> </svg> <div class="percent"> <span class="percent__int">0.</span> <span class="percent__dec">00</span> </div> </div> <span class="label">Transparent</span> </li> + <li class="note-display" Data-note ="9.27"> <div class="circle"> < SVG width="84" height="84" class="circle__svg"> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle> </svg> <div class="percent"> <span class="percent__int">0.</span> <span class="percent__dec">00</span> </div> </div> <span class="label">Reasonable</span> </li> + <li class="note-display" Data-note ="6.93"> <div class="circle"> < SVG width="84" height="84" class="circle__svg"> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle> </svg> <div class="percent"> <span class="percent__int">0.</span> <span class="percent__dec">00</span> </div> </div> <span class="label">Usable</span> </li> + <li class="note-display" Data-note ="8.72"> <div class="circle"> < SVG width="84" height="84" class="circle__svg"> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle> </svg> <div class="percent"> <span class="percent__int">0.</span> <span class="percent__dec">00</span> </div> </div> <span class="label">Exemplary</span> </li> </ul>

The parseFloat method converts the string returned by display.dataset. Note to a floating point number. Offset represents the percentage missing when the maximum value is reached. Thus, for 7.50 note, we would have (10-7.50) / 10 = 0.25, which means that circumference length should be shifted by 25% of its value:

let note = parseFloat(display.dataset.note);
let offset = circumference * (10 - note) / 10;

Update the scripts. Js:

const displays = document.querySelectorAll('.note-display');
const transitionDuration = 900;

displays.forEach(display => {
  let progress = display.querySelector('.circle__progress--fill');
  let radius = progress.r.baseVal.value;
  let circumference = 2 * Math.PI * radius;
+ let note = parseFloat(display.dataset.note);
+ let offset = circumference * (10 - note) / 10;

  progress.style.setProperty('--initialStroke', circumference);
  progress.style.setProperty('--transitionDuration', `${transitionDuration}ms`);

+ setTimeout(() => progress.style.strokeDashoffset = offset, 100);
});

The sroke attribute is converted to the note value

Before proceeding, let’s extract the stoke transform into its own method:

const displays = document.querySelectorAll('.note-display'); const transitionDuration = 900; displays.forEach(display => { - let progress = display.querySelector('.circle__progress--fill'); - let radius = progress.r.baseVal.value; - let circumference = 2 * Math.PI * radius; let note = parseFloat(display.dataset.note); - let offset = circumference * (10 - note) / 10; - progress.style.setProperty('--initialStroke', circumference); - progress.style.setProperty('--transitionDuration', `${transitionDuration}ms`); - setTimeout(() => progress.style.strokeDashoffset = offset, 100); + strokeTransition(display, note); }); + function strokeTransition(display, note) { + let progress = display.querySelector('.circle__progress--fill'); + let radius = progress.r.baseVal.value; + let circumference = 2 * Math.PI * radius; + let offset = circumference * (10 - note) / 10; + progress.style.setProperty('--initialStroke', circumference); + progress.style.setProperty('--transitionDuration', `${transitionDuration}ms`); + setTimeout(() => progress.style.strokeDashoffset = offset, 100); +}

Increment of attention

One more thing is to convert the note from 0.00 to the final note value. The first thing to do is to separate integers from fractional values. You can use the string method split(). They are then converted to numbers and passed as arguments to the increaseNumber() function, showing up correctly on the corresponding element with integer and decimal flags.

const displays = document.querySelectorAll('.note-display');
const transitionDuration = 900;

displays.forEach(display => {
  let note = parseFloat(display.dataset.note);
+ let [int, dec] = display.dataset.note.split('.');
+ [int, dec] = [Number(int), Number(dec)];

  strokeTransition(display, note);

+ increaseNumber(display, int, 'int');
+ increaseNumber(display, dec, 'dec');
});

In the increaseNumber() function, whether we choose the.percent__int or.percent__dec element depends on the className and whether the output should contain decimal points. Next, set TransitionDuration to 900 milliseconds. Now, the animation represents the numbers from 0 to 7, and the duration must be divided by Note 900/7 = 128.57ms. The result indicates how long each incremental iteration will take. This means that setInterval will fire every 128.57ms.

Once you’ve set these variables, define the setInterval. The counter variable is appended to the element as text and is incremented with each iteration:

function increaseNumber(display, number, className) {
  let element = display.querySelector(`.percent__${className}`),
      decPoint = className === 'int' ? '.' : '',
      interval = transitionDuration / number,
      counter = 0;

  let increaseInterval = setInterval(() => {
    element.textContent = counter + decPoint;
    counter++;
  }, interval);
}

Figure: Counts grow

Cool! It does increase the count, but it plays on an infinite loop. When the note reaches the value we want, we also need to clear the setInterval. This can be done using the clearInterval function:

function increaseNumber(display, number, className) {
  let element = display.querySelector(`.percent__${className}`),
      decPoint = className === 'int' ? '.' : '',
      interval = transitionDuration / number,
      counter = 0;

  let increaseInterval = setInterval(() => {
+   if (counter === number) { window.clearInterval(increaseInterval); }

    element.textContent = counter + decPoint;
    counter++;
  }, interval);
}

Figure: Finally finished

Now, the number is updated to the note value and cleared using the clearInterval() function.

This is the end of this tutorial, hope you enjoyed it!

If you want to develop something more interactive, check out the Memory Game Tutorial, created using Vanilla JavaScript. It covers basic HTML5, CSS3, and JavaScript concepts such as positioning, perspective, transformation, Flexbox, event handling, timeout, and triples.

Happy coding! 🌹


The first send WeChat messages public number: Jingchengyideng



Welcome to scan the two-dimensional code to pay attention to the public number, every day to push you fresh front-end technology articles