惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

MyScale Blog
MyScale Blog
U
Unit 42
The Register - Security
The Register - Security
S
Security Affairs
博客园 - 【当耐特】
Latest news
Latest news
爱范儿
爱范儿
T
The Exploit Database - CXSecurity.com
F
Full Disclosure
C
Cisco Blogs
宝玉的分享
宝玉的分享
C
Cyber Attacks, Cyber Crime and Cyber Security
L
LangChain Blog
P
Privacy & Cybersecurity Law Blog
腾讯CDC
C
CXSECURITY Database RSS Feed - CXSecurity.com
V
Vulnerabilities – Threatpost
Jina AI
Jina AI
Apple Machine Learning Research
Apple Machine Learning Research
博客园 - 叶小钗
www.infosecurity-magazine.com
www.infosecurity-magazine.com
博客园_首页
博客园 - 三生石上(FineUI控件)
D
DataBreaches.Net
WordPress大学
WordPress大学
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Microsoft Security Blog
Microsoft Security Blog
N
News and Events Feed by Topic
Recorded Future
Recorded Future
Scott Helme
Scott Helme
Hacker News: Ask HN
Hacker News: Ask HN
Webroot Blog
Webroot Blog
AWS News Blog
AWS News Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
人人都是产品经理
人人都是产品经理
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
T
Tor Project blog
F
Fortinet All Blogs
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
H
Hacker News: Front Page
J
Java Code Geeks
A
About on SuperTechFans
The GitHub Blog
The GitHub Blog
博客园 - 聂微东
Last Week in AI
Last Week in AI
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
W
WeLiveSecurity
V2EX - 技术
V2EX - 技术
T
Troy Hunt's Blog
Attack and Defense Labs
Attack and Defense Labs

CloudCannon Blog

Building with AI: Git-based vs headless vs traditional CMS CloudCannon + Astro: performance meets powerful content management Introducing the Astro Component Starter Introducing Jetstream — built on the Astro Component Starter Why we switched to the system font stack Redesigning CloudCannon’s docs with Diátaxis, Lume, and Pagefind Make content editing more visual: upgraded Editable Regions How Configuration Mode makes building editing interfaces easy Your hosting just got an upgrade (and a price cut) Custom testing domains for professional branding Keep your content consistent with input validation Managing multilingual content in CloudCannon Simplify team publishing with conflict resolution and domain tools Open Beta: Publishing Conflict Resolution Getting started with CloudCannon and Astro: Bookshop, components, and live editing Welcome to the CloudCannon Community! Omnichannel delivery is just marketing spin from API-based CMS companies Getting started with CloudCannon and Astro: Snippets and Collections Managing digital assets in CloudCannon: a guide to smart asset storage Understanding CloudCannon's branching workflows and Projects: a complete guide What is a static website? CloudCannon’s 2024 wrapped Getting started with CloudCannon and Astro: WYSIWYG blogging Jamstack vs. WordPress: reasons to make the change The top five static site generators for 2025 (and when to use them!) Free Jekyll themes for 2025: ten great community options Eleventy (11ty) vs. Hugo How to set up WYSIWYG editing with MkDocs Material The rise of static-first websites: why major brands are making the switch Watching your Core Web Vitals on Jamstack Understanding the difference between static, dynamic, and hybrid websites Looking for an alternative to Netlify CMS or Decap CMS? Designing components for your website editors: a CloudCannon case study Does my website look big in this? Six tips to lower your page weight Content is sacred — so own your revision history The eternal balancing act: load time vs. delay time Streamlined Headless Mode, Unified Configuration, and live data editing What is a headless CMS? Looking for a TinaCMS or Tina Cloud alternative? The ultimate guide to Hugo Sections Coming soon: Live config editing and data reloading Faster publishing workflows out now! Why information architecture matters for your website Website UX vs SEO: picking your battles Easily manage your multilingual Astro site in CloudCannon How you can optimize publishing workflows for your content team How you can optimize your CMS for SEO success How you can optimize your Content Editor for long-form articles How you can optimize your Visual Editor for page building Secure, swift, and stable: static sites for the financial sector Enhanced flexibility for teams with Custom Permissions Building static sites that scale The Inaugural 11ty International Symposium on Making Web Sites Real Good How to manage hundreds of connected websites with a Git-based headless CMS How we’re building CloudCannon for accessibility CloudCannon’s new editing improvements are here! Open Beta | New ways to collaborate on editing your websites Top 11 free Eleventy themes for 2024 Top 10 free Astro themes to use in 2024 Why choose a Git-based headless CMS over a monolithic DXP in 2024? Learning web development: a self-guided roadmap Partner Site of the Month: Blüthner Piano Centre, by Winteractive CloudCannon’s 2023 wrapup Let’s create a microblog with visual editing using Bookshop and Eleventy Update and visualize your branches with CloudCannon Projects What is a Git-based CMS and why you should use one CloudCannon secures SOC 2 certification The complete guide to growing your web development agency Automatically optimize your images with Eleventy Image and CloudCannon Share components and syndicate content with Site Mounting Partner Site of the Month: Cru Uncorked, by Ed Meehan New web component for responsive HTML tables Wrapping up HugoConf 2023 Partner Site of the Month: Van Dillen Antieke Bouwmaterialen, by Fulldev How to become a freelance web developer: a comprehensive guide Q3 2023: CloudCannon features and improvements 22 ways to deliver more value to your web development clients Partner Site of the Month: DC Gay Flag Football League, by Ed Cupaioli A new way to configure your CloudCannon sites CloudCannon — the official CMS partner of Eleventy Full CloudCannon support for Nuxt static sites Partner Site of the Month: Stadium Bike, by Insight Creative, Inc. HugoConf 2023, brought to you by CloudCannon DAM Support for Cloudflare R2 and DigitalOcean Spaces Eleventy (11ty) vs. Gatsby in 2023 – which SSG is best for you? How CloudCannon’s live editing works with Astro and Bookshop Partner Site of the Month: Fiducian, by Croissant & Baguette Eleventy (11ty) vs. Astro How to customize CloudCannon’s Client Sharing interface Let anyone, anywhere, edit your CloudCannon sites Top 23 free Astro themes for building out-of-this-world static sites in 2023 How Jampack optimizes our Eleventy website and improves performance Astro vs. Next.js CloudCannon.com is now built with Eleventy! Out-of-this-world support for all Astro users Introducing the CloudCannon Partner Program Full CloudCannon support for Gatsby Top 10 Free SvelteKit Themes for Building Lighting-Fast Static Sites in 2023 Enhanced CloudCannon support for Next.js users Upcoming CloudCannon Interface improvements (Open Beta)
Google's transforming material icons in HTML and CSS
2014-12-05 · via CloudCannon Blog

This week I am deconstructing the Delightful Details from Google's Material Design. It's not built in HTML and CSS but that's not important. What's important is that we can.

An image version of Google's Delightful Details

If you haven't read Google's Material Design Spec I highly recommend it. It's a well crafted design spec with a lot of good information. I have followed the different android specs over the years but this one excites me as it includes information for larger screens.

What is it? Direct link to this section

This is the finished clone of the Delightful Details. Note that it isn't exactly the same but it proves that it can be done in HTML and CSS. You click on each section to toggle between icons.

<div class="material-grid">
<section class="material-green">
<div class="material-icon hamburger">
<span class="first"></span>
<span class="second"></span>
<span class="third"></span>
</div>
</section>

<section class="material-amber">
<div class="material-icon play">
<span class="first"></span>
<span class="second"></span>
<span class="third"></span>
</div>
</section>

<section class="material-blue">
<div class="material-icon failed-loader">
<span class="first"></span>
<span class="second"></span>
<span class="third"></span>
</div>
</section>

<section class="material-red">
<div class="material-icon plus-one">
<span class="first"></span>
<span class="second"></span>
<span class="third"></span>
</div>
</section>
</div>

Now let's break it down.

The Grid Direct link to this section

This is just a square grid cut in four with different colours. My colours differ slightly as I chose to use the 500 strength colours from the material colour palette.

<div class="material-grid">
<section class="material-green">
</section>

<section class="material-amber">
</section>

<section class="material-blue">
</section>

<section class="material-red">
</section>
</div>

Here's the CSS for the grid, the Roboto font will come in handy later. The user-select will allow us to click the elements without selecting them.

.material-grid {
width: 360px;
height: 360px;
font-family: "Roboto", Arial;
}

.material-grid section {
width: 50%;
height: 50%;
float: left;
position: relative;
overflow: hidden;
cursor: pointer;
user-select: none;
}

Here's another nice effect for clicking the grid section. My inspiration comes from the material design spec again; it adds a circular active effect on click. Try clicking any of the grids on this page (you will need to hold it for a bit).

@keyframes materialResponse {
0% {
width: 0;
height: 0;
margin: 0;
background: rgba(255,255,255,0.1);
}
100% {
width: 250%;
height: 250%;
margin: -125%;
background: rgba(255,255,255,0.4);
}
}

.material-grid section:before {
content: "";
position: absolute;
top: 50%;
left: 50%;
border-radius: 999px;
cursor: pointer;
}

.material-grid section:active:before {
animation: materialResponse 0.4s ease;
z-index: 2;
}

.material-green { background: #4CAF50; }
.material-amber { background: #FFC107; }
.material-blue { background: #2196F3; }
.material-red { background: #F44336; }

The Icons Direct link to this section

To recreate the icons in the grids I have chosen to use a div with three span tags. With these elements I will create the following icons:

  • The infamous hamburger icon
  • A back arrow
  • Pause
  • Play
  • Stop
  • The animated loader (a failed attempt)
  • +1 Icon
  • +2 Icon

By creating all of these with the same elements I can ensure that I can smoothly transition between any of them. Each one having it's own class to add to the .material-icon element I will break them down in the order listed above (also the order I built them).

<div class="material-icon">
<span class="first"></span>
<span class="second"></span>
<span class="third"></span>
</div>

Here is the base CSS used to construct my icons:

.material-icon {
width: 70px;
height: 70px;
margin: 55px;
position: relative;
}

.material-icon span,
.material-icon
{
transition: all 500ms ease;
}

.material-icon span {
background-color: #fff;
display: block;
position: absolute;
top: 0;
left: 0;
width: 100%;
}

The infamous hamburger icon Direct link to this section

The hamburger is an on going debate in the UX community but I leave that for another day. All I need to do for this icon is create three horizontal bars of equal height. I will move the second span to the middle and the third to the bottom.

.hamburger span {
height: 10px;
-webkit-transform: translateY(29px);
}

.hamburger .second {
-webkit-transform: translateY(0);
}

.hamburger .third{
-webkit-transform: translateY(60px);
}

Now you may be asking, why not use absolute positioning here? By using transforms we get some fantastic performance gains as well as sub-pixel positioning on our transitions. Paul Irish breaks this down really well in his article on absolute positioning vs translate.

A back arrow Direct link to this section

I will build the back arrow to be an arrow facing right, then rotate the whole container 180 degrees. This will give we the same effect as the video and allow me to focus each animation separately. The first span is exactly the same so I will combine that selector. The second and third are rotated 45 degrees and need to be moved into place. You can click this one to see the animation.

/* This part is added to the hamburger css so they have the same height and positioning. */
.hamburger span, .arrow span {
height: 10px;
-webkit-transform: translateY(29px);
}

/* I also need to move it back a bit to keep it centered. */
.arrow {
-webkit-transform: rotate(180deg) translateX(-3px);
}

.arrow .second,
.arrow .third
{
-webkit-transform: rotate(-45deg) translateY(56px);
width: 40px;
}

.arrow .second {
-webkit-transform: rotate(45deg) translateY(-15px) translateX(40px);
}

That's the green section all done. Let's move on to the amber section.

Pause Direct link to this section

The pause is just as easy as the hamburger; It's just two vertical bars. I decided to rotate it 90deg and have two horizontal bars for the animation to play.

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

.pause span {
height: 25px;
}

.pause .second {
-webkit-transform: translateY(45px);
}

Play… Direct link to this section

This one took me a while to think about. You can probably imagine me trying to stick three rectangles together to create a triangle. Hmmm… I'll come back to this one after stop.

Stop Direct link to this section

This one was obvious everyone who knows CSS knows how to create a coloured square. I did however use two half blocks so the animation to pause would look like it's splitting from the middle.

.stop {
-webkit-transform: rotate(270deg);
}

.stop span {
height: 35px;
}

.stop .second {
height: 45px;
-webkit-transform: translateY(25px);
}

Play Again Direct link to this section

After doing stop and looking at the original animation closely I realised that I needed something else to achieve this. I employed the same technique from my Facebook content placeholder post to mask the icon into a triangle shape.

Here's my mask, no extra elements required. I default it to sitting on top and bottom so my earlier icons are not effected.

.material-icon:before,
.material-icon:after
{
background: transparent;
width: 0;
height: 0;
display: block;
position: relative;
width: 100px;
height: 40px;
z-index: 1;
transform: translateY(-50px);
transition: all 500ms ease;
color: transparent;
}

/* Colour the masks the same colour as the grids */
.material-green,
.material-green .material-icon:before,
.material-green .material-icon:after
{ background: #4CAF50; }

.material-amber,
.material-amber .material-icon:before,
.material-amber .material-icon:after
{ background: #FFC107; }

.material-blue,
.material-blue .material-icon:before,
.material-blue .material-icon:after
{ background: #2196F3; }

.material-red,
.material-red .material-icon:before,
.material-red .material-icon:after
{ background: #F44336; }

Here's the additional CSS for the play icon. It rotates the container so that it has the animation on change and rotates our masks into place to create a triangle.

/* We want to use the square and mask it so we might as well use the same CSS as the stop */
.stop span, .play span {
height: 35px;
}

.stop .second, .play .second {
height: 45px;
-webkit-transform: translateY(25px);
}

.play {
-webkit-transform: rotate(180deg);
}

.play:before {
-webkit-transform: translateY(-24px) translateX(-17px) rotate(-26deg);
}

.play:after {
-webkit-transform: translateY(11px) translateX(-22px) rotate(26deg);
}

That's the amber section all done. Let's move on to the blue loader section.

The animated loader a failed attempt Direct link to this section

I'm going to be honest, I was on a bit of a high getting the last two parts done. The loader did not seem like a huge leap as I could use all the same tactics and pair it with an infinite CSS animation. As you can see I gave up getting the animations anything close to perfect:

.stop {
-webkit-transform: rotate(270deg);
}

.stop span {
height: 35px;
}

.stop .second {
height: 45px;
-webkit-transform: translateY(25px);
}

I will break each step down anyway. Since releasing this post, a good article using SVG and CSS animations has popped up to implement the loader.

@keyframes spin {
from {transform:rotate(0deg);}
to {transform:rotate(360deg);}
}

@keyframes leader {
0% { transform: rotate(0); }
12% {
transform: rotate(90deg);
border-bottom-color: transparent;
}
12.00001% {
border-bottom-color: #fff;
}
25% {
transform: rotate(180deg);
border-bottom-color: #fff;
}
37.5% {
transform: rotate(180deg);
border-bottom-color: #fff;
border-right-color: transparent;
}
37.50001%, 50% {
transform: rotate(180deg);
border-bottom-color: transparent;
border-right-color: transparent;
}

/* --- */
67.5% {
transform: rotate(270deg);
border-bottom-color: transparent;
border-right-color: transparent;
}
67.50001% {
transform: rotate(270deg);
border-bottom-color: #fff;
border-right-color: transparent;
}
75%, 87.5% {
transform: rotate(360deg);
border-bottom-color: #fff;
border-right-color: transparent;
}
87.50001%, 100% {
transform: rotate(360deg);
border-bottom-color: transparent;
border-right-color: transparent;
}
}

@keyframes trailer {
0% { transform: rotate(0); }
25% { transform: rotate(0); }
37.5% {
transform: rotate(90deg);
}
50% {
transform: rotate(180deg);
border-bottom-color: transparent;
border-right-color: transparent;
}
75% { transform: rotate(180deg); }
87.5% {
transform: rotate(270deg);
}
100% {
transform: rotate(360deg);
border-bottom-color: transparent;
border-right-color: transparent;
}
}

.failed-loader {
animation: spin 3s infinite 0.5s linear;
}

.failed-loader span {
width: 58px;
height: 58px;
border: 6px solid #fff;
background: transparent;
border-radius: 70px;
border-top-color: transparent;
border-bottom-color: transparent;
border-right-color: transparent;
animation: trailer 1.5s linear 0.5s infinite;
}

.failed-loader .first {
animation: leader 1.5s linear 0.5s infinite;
}

.failed-loader .third {
display: none;
}

Here are the steps I took to recreate the loader. To get the general shape I created a div with border on three sides (1st version) then added a border radius (2nd version). The third step was to add a constant rotation to the outer element (3rd version). The next image is a bit weird on it's own; it rotates one of the spans and while adding and removing one of the borders. This image paired with another span rotating behind it gives the growing and shrinking look.

If I were to build it again I would have had a full circle in white and used the masks from before. The masks would use the same technique to grow and shrink. This would allow the white segment to get much smaller like in the video. This was not possible in the previous version as the minimum size was set by one of the edges.

+1 and +2 Icon Direct link to this section

I was finally up to my last segment and I was cut down by the mighty loader. I decided to start with the plus. Very similar to techniques before but I overlap them instead of putting them parallel. For the +2 icon I can rotate the two elements of the plus 90 degrees.

.plus-one .first, .plus-two .first,
.plus-one .second, .plus-two .second
{
width: 30px;
height: 6px;
-webkit-transform: translateY(32px);
}

.plus-one .second,
.plus-two .first
{
-webkit-transform: translateY(32px) rotate(90deg);
}

.plus-two .second {
-webkit-transform: translateY(32px) rotate(180deg);
}

For the number, I mentioned earlier that the Roboto font will come in handy. I figured the easiest way to add the numbers was to make the content of the masks "1" and "2". Then all I need to do is position the masks to the right of the plus.

.material-icon:before { content: "1"; }
.material-icon:after { content: "2"; }

.plus-one:before,
.plus-two:after
{
color: #fff;
background: transparent !important;
font-size: 52px;
line-height: 70px;
font-weight: 800;
transform: translateY(-5px) translateX(30px);
}

.plus-two:after {
transform: translateY(-45px) translateX(35px);
}

Once I added the CSS to position it I noticed a really nice effect of the masks fading back to their original positions. That wasn't an intended effect but I liked it so much I kept it like that. It feels like the plus is a level and it is pulling the numbers in and out.

For anyone looking for the original number change animation, here is a codepen by Lucas Bebber which does exactly that.

Why would I ever use this? Direct link to this section

Android used to have a rule, maybe they still do, that for every negative experience a user has there must be three positive reactions to make up for it. These are just a few examples that will truly delight a user and these are the things they remember. Using things like this will show the user how the interface is changing rather relying on education.

That's it Direct link to this section

This Deconstruction took me a bit longer than expected which is why there wasn't one last week. I hope to use some of these techniques soon. I am thinking of writing a tutorial to demonstrate custom controls on HTML5 videos which will feature the play, pause and stop. Definitely check out Google's Material Design Spec, it's full of great designs and guidelines to think about. If you have any questions or you fixed my loading symbol add a comment below.