前言
本文是笔者写CSS时常用的套路。不论效果再怎么华丽,万变不离其宗。
1、交错动画
有时候,我们需要给多个元素添加同一个动画,播放后,不难发现它们会一起运动,一起结束,这样就会显得很平淡无奇。那么如何将动画变得稍微有趣一点呢?很简单,既然它们都是同一时刻开始运动的,那么让它们不在同一时刻运动不就可以了吗。如何让它们不在同一时刻运动呢?注意到CSS动画有延迟(delay
)这一属性。举个栗子,比如有十个元素播放十个动画,将第二个元素的动画播放时间设定为比第一个元素晚0.5秒(也就是将延迟设为0.5秒),其他元素以此类推,这样它们就会错开来,形成一种独特的视觉效果。
这就是所谓的交错动画:通过设置不同的延迟时间,达到动画交错播放的效果。本demo地址:https://codepen.io/alphardex/pen/XWWWBmQ
<div class="loading">
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
</div>
body {
display: flex;
height: 100vh;
justify-content: center;
align-items: center;
background: #222;
}
.loading {
$colors: #7ef9ff, #89cff0, #4682b4, #0f52ba, `#000080;
display: flex;
animation-delay: 1s;
.dot {
position: relative;
width: 2em;
height: 2em;
margin: 0.8em;
border-radius: 50%;
&::before {
position: absolute;
content: "";
width: 100%;
height: 100%;
background: inherit;
border-radius: inherit;
animation: wave 2s ease-out infinite;
}
@for $i from 1 through 5 {
&:nth-child(#{$i}) {
background: nth($colors, $i);
&::before {
animation-delay: $i * 0.2s;
}
}
}
}
}
@keyframes wave {
50%,
75% {
transform: scale(2.5);
}
80%,
100% {
opacity: 0;
}
}
2、用JS分割文本
还有一种经常用到的玩法:用JS将句子或单词分割成字母,并给每个字母加上不同延时的动画,同样也很华丽。<p class="landIn">Ano hi watashitachi mada shiranai no Fushigi no monogatari desu.</p>
@import url("https://fonts.googleapis.com/css?family=Lora:400,400i,700");
body {
display: flex;
flex-direction: column;
height: 100vh;
justify-content: center;
align-items: center;
background-image: linear-gradient(rgba(16, 16, 16, 0.8),
rgba(16, 16, 16, 0.8)),
url(https://i.loli.net/2019/10/18/buDT4YS6zUMfHst.jpg);
background-size: cover;
}
p {
margin: 0 9em;
font-size: 2em;
font-weight: 600;
}
.landIn {
display: flex;
flex-wrap: wrap;
line-height: 1.8;
color: white;
font-family: Lora, serif;
white-space: pre;
span {
animation: landIn 0.8s ease-out both;
}
}
@keyframes landIn {
from {
opacity: 0;
transform: translateY(-20%);
}
to {
opacity: 1;
transform: translateY(0);
}
}
let landInTexts = document.querySelectorAll(".landIn");
landInTexts.forEach(landInText => {
let letters = landInText.textContent.split("");
landInText.textContent = "";
letters.forEach((letter, i) => {
let span = document.createElement("span");
span.textContent = letter;
span.style.animationDelay = `${i * 0.05}s`;
landInText.append(span);
});
});
本demo地址:https://codepen.io/alphardex/full/KKwvKGY一般我们都是从第一个元素开始交错的。但如果要从中间元素开始交错的话,就要给当前元素的延时各加上一个值,这个值就是中间元素的下标到当前元素的下标的距离(也就是下标之差的绝对值)与步长的乘积,即:delay + Math.abs(i - middle) * step
,其中中间元素的下标middle = letters.filter(e => e !== "").length / 2
本demo地址:https://codepen.io/alphardex/full/eYYMYXJ所有有交错特性的动画都在这儿
<div class="reveal">sword art online</div>
@import url("https://fonts.googleapis.com/css?family=Raleway:400,400i,700");
body {
display: flex;
height: 100vh;
justify-content: center;
align-items: center;
text-align: center;
background: #222;
}
.reveal {
position: relative;
display: flex;
color: #6ee1f5;
font-size: 2em;
font-family: Raleway, sans-serif;
letter-spacing: 3px;
text-transform: uppercase;
white-space: pre;
span {
opacity: 0;
transform: scale(0);
animation: fadeIn 2.4s forwards;
}
&::before,
&::after {
position: absolute;
content: "";
top: 0;
bottom: 0;
width: 2px;
height: 100%;
background: white;
opacity: 0;
transform: scale(0);
}
&::before {
left: 50%;
animation: slideLeft 1.5s cubic-bezier(0.7, -0.6, 0.3, 1.5) forwards;
}
&::after {
right: 50%;
animation: slideRight 1.5s cubic-bezier(0.7, -0.6, 0.3, 1.5) forwards;
}
}
@keyframes fadeIn {
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes slideLeft {
to {
left: -6%;
opacity: 1;
transform: scale(0.9);
}
}
@keyframes slideRight {
to {
right: -6%;
opacity: 1;
transform: scale(0.9);
}
}
let duration = 0.8;
let delay = 0.3;
let revealText = document.querySelector(".reveal");
let letters = revealText.textContent.split("");
revealText.textContent = "";
let middle = letters.filter(e => e !== " ").length / 2;
letters.forEach((letter, i) => {
let span = document.createElement("span");
span.textContent = letter;
span.style.animationDelay = `${delay + Math.abs(i - middle) * 0.1}s`;
revealText.append(span);
});
3、随机粒子动画
说到随机性,我们可以实现一种更疯狂的效果:给几百个粒子添加交错动画,并且交错时间随机,位置大小也都是随机。如此一来我们就能用纯CSS模拟出下雪的效果。又到了白色相簿的季节呢~为什么你写CSS这么熟练啊?本demo地址:https://codepen.io/alphardex/full/dyPorwJ
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
<div class="snow"></div>
body {
height: 100vh;
background: radial-gradient(ellipse at bottom, #1b2735 0%, #090a0f 100%);
overflow: hidden;
filter: drop-shadow(0 0 10px white);
}
@function random_range($min, $max) {
$rand: random();
$random_range: $min + floor($rand * (($max - $min) + 1));
@return $random_range;
}
.snow {
$total: 200;
position: absolute;
width: 10px;
height: 10px;
background: white;
border-radius: 50%;
@for $i from 1 through $total {
$random-x: random(1000000) * 0.0001vw;
$random-offset: random_range(-100000, 100000) * 0.0001vw;
$random-x-end: $random-x + $random-offset;
$random-x-end-yoyo: $random-x + ($random-offset / 2);
$random-yoyo-time: random_range(30000, 80000) / 100000;
$random-yoyo-y: $random-yoyo-time * 100vh;
$random-scale: random(10000) * 0.0001;
$fall-duration: random_range(10, 30) * 1s;
$fall-delay: random(30) * -1s;
&:nth-child(#{$i}) {
opacity: random(10000) * 0.0001;
transform: translate($random-x, -10px) scale($random-scale);
animation: fall-#{$i} $fall-duration $fall-delay linear infinite;
}
@keyframes fall-#{$i} {
#{percentage($random-yoyo-time)} {
transform: translate($random-x-end, $random-yoyo-y) scale($random-scale);
}
to {
transform: translate($random-x-end-yoyo, 100vh) scale($random-scale);
}
}
}
}
伪类和伪元素
4、伪类
HTML元素的状态是可以动态变化的。举个栗子,当你的鼠标悬浮到一个按钮上时,按钮就会变成“悬浮”状态,这时我们就可以利用伪类:hover
来选中这一状态的按钮,并对其样式进行改变。:hover
是笔者最最常用的一个伪类。还有一个很常用的伪类是:nth-child
,用于选中元素的某一个子元素。其他的类似:focus
、:focus-within
等也有一定的使用。本demo地址:https://codepen.io/alphardex/pen/pooYKVa
<button
data-text="Start"
class="btn btn-primary btn-ghost btn-border-stroke btn-text-float-up"
>
<div class="btn-borders">
<div class="border-top"></div>
<div class="border-right"></div>
<div class="border-bottom"></div>
<div class="border-left"></div>
</div>
<span class="btn-text">Start</span>
</button>
@import url(https://fonts.googleapis.com/css?family=Lato);
body {
display: flex;
height: 100vh;
justify-content: center;
align-items: center;
text-align: center;
background: #222;
}
.btn {
--hue: 190;
--ease-in-duration: 0.25s;
--ease-out-duration: 0.65s;
--ease-out-delay: var(--ease-in-duration);
position: relative;
padding: 1rem 3rem;
font-size: 1rem;
line-height: 1.5;
color: white;
text-decoration: none;
background-color: hsl(var(--hue), 100%, 41%);
border: 1px solid hsl(var(--hue), 100%, 41%);
outline: transparent;
overflow: hidden;
cursor: pointer;
user-select: none;
white-space: nowrap;
transition: 0.25s;
&:hover {
background: hsl(var(--hue), 100%, 31%);
}
&-primary {
--hue: 171;
}
&-ghost {
color: hsl(var(--hue), 100%, 41%);
background-color: transparent;
border-color: hsl(var(--hue), 100%, 41%);
&:hover {
color: white;
}
}
&-border-stroke {
border-color: hsla(var(--hue), 100%, 41%, 0.35);
.btn-borders {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
.border-top {
position: absolute;
top: 0;
width: 100%;
height: 1px;
background: hsl(var(--hue), 100%, 41%);
transform: scaleX(0);
transform-origin: left;
}
.border-right {
position: absolute;
right: 0;
width: 1px;
height: 100%;
background: hsl(var(--hue), 100%, 41%);
transform: scaleY(0);
transform-origin: bottom;
}
.border-bottom {
position: absolute;
bottom: 0;
width: 100%;
height: 1px;
background: hsl(var(--hue), 100%, 41%);
transform: scaleX(0);
transform-origin: left;
}
.border-left {
position: absolute;
left: 0;
width: 1px;
height: 100%;
background: hsl(var(--hue), 100%, 41%);
transform: scaleY(0);
transform-origin: bottom;
}
// when unhover, ease-in top, right; ease-out bottom, left
.border-left {
transition: var(--ease-out-duration) var(--ease-out-delay)
cubic-bezier(0.2, 1, 0.2, 1);
}
.border-bottom {
transition: var(--ease-out-duration) var(--ease-out-delay)
cubic-bezier(0.2, 1, 0.2, 1);
}
.border-right {
transition: var(--ease-in-duration) cubic-bezier(1, 0, 0.8, 0);
}
.border-top {
transition: var(--ease-in-duration) cubic-bezier(1, 0, 0.8, 0);
}
}
&:hover {
color: hsl(var(--hue), 100%, 41%);
background: transparent;
.border-top,
.border-bottom {
transform: scaleX(1);
}
.border-left,
.border-right {
transform: scaleY(1);
}
// when hover, ease-in bottom, left; ease-out top, right
.border-left {
transition: var(--ease-in-duration) cubic-bezier(1, 0, 0.8, 0);
}
.border-bottom {
transition: var(--ease-in-duration) cubic-bezier(1, 0, 0.8, 0);
}
.border-right {
transition: var(--ease-out-duration) var(--ease-out-delay)
cubic-bezier(0.2, 1, 0.2, 1);
}
.border-top {
transition: var(--ease-out-duration) var(--ease-out-delay)
cubic-bezier(0.2, 1, 0.2, 1);
}
}
}
&-text-float-up {
&::after {
position: absolute;
content: attr(data-text);
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
transform: translateY(35%);
transition: 0.25s ease-out;
}
// when hover, ease-in top-text; ease-out bottom-text
.btn-text {
display: block;
transition: 0.75s 0.1s cubic-bezier(0.2, 1, 0.2, 1);
}
&:hover {
// when hover, ease-in bottom-text; ease-out top-text
.btn-text {
opacity: 0;
transform: translateY(-25%);
transition: 0.25s ease-out;
}
&::after {
opacity: 1;
transform: translateY(0);
transition: 0.75s 0.1s cubic-bezier(0.2, 1, 0.2, 1);
}
}
}
}
5、绝对定位实现多重边框
谁规定按钮只能有一套边框的?利用绝对定位和padding
,我们可以给按钮做出3套大小不一的边框来,这样效果更炫了。
本demo地址:https://codepen.io/alphardex/full/ZEYXomW
<button class="btn btn-primary btn-ghost btn-multiple-border-stroke">
<div class="btn-borders-group">
<div class="border-top"></div>
<div class="border-right"></div>
<div class="border-bottom"></div>
<div class="border-left"></div>
</div>
<div class="btn-borders-group">
<div class="border-top"></div>
<div class="border-right"></div>
<div class="border-bottom"></div>
<div class="border-left"></div>
</div>
<div class="btn-borders-group">
<div class="border-top"></div>
<div class="border-right"></div>
<div class="border-bottom"></div>
<div class="border-left"></div>
</div>
<span class="btn-text">Start</span>
</button>
@import url(https://fonts.googleapis.com/css?family=Lato);
body {
display: flex;
height: 100vh;
justify-content: center;
align-items: center;
text-align: center;
background: #222;
}
.btn {
--hue: 190;
--ease-in-duration: 0.25s;
--ease-out-duration: 0.65s;
--ease-out-delay: var(--ease-in-duration);
position: relative;
padding: 1rem 3rem;
font-size: 1rem;
line-height: 1.5;
color: white;
text-decoration: none;
background-color: hsl(var(--hue), 100%, 41%);
border: 1px solid hsl(var(--hue), 100%, 41%);
outline: transparent;
cursor: pointer;
user-select: none;
white-space: nowrap;
transition: 0.25s;
&:hover {
background: hsl(var(--hue), 100%, 31%);
}
&-primary {
--hue: 171;
}
&-ghost {
color: hsl(var(--hue), 100%, 41%);
background-color: transparent;
border-color: hsl(var(--hue), 100%, 41%);
&:hover {
color: white;
}
}
&-multiple-border-stroke {
border-color: transparent;
.btn-borders-group {
position: absolute;
top: 0;
left: 0;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
border: 1px solid hsla(var(--hue), 100%, 41%, 0.35);
&:nth-child(1) {
left: -8px;
padding: 0 8px;
}
&:nth-child(2) {
top: -8px;
padding: 8px 0;
}
&:nth-child(3) {
top: -4px;
left: -4px;
padding: 4px;
}
.border-top {
position: absolute;
top: 0;
width: 100%;
height: 1px;
background: hsl(var(--hue), 100%, 41%);
transform: scaleX(0);
transform-origin: left;
}
.border-right {
position: absolute;
right: 0;
width: 1px;
height: 100%;
background: hsl(var(--hue), 100%, 41%);
transform: scaleY(0);
transform-origin: bottom;
}
.border-bottom {
position: absolute;
bottom: 0;
width: 100%;
height: 1px;
background: hsl(var(--hue), 100%, 41%);
transform: scaleX(0);
transform-origin: left;
}
.border-left {
position: absolute;
left: 0;
width: 1px;
height: 100%;
background: hsl(var(--hue), 100%, 41%);
transform: scaleY(0);
transform-origin: bottom;
}
// when unhover, ease-in top, right; ease-out bottom, left
.border-left {
transition: var(--ease-out-duration) var(--ease-out-delay) cubic-bezier(0.2, 1, 0.2, 1);
}
.border-bottom {
transition: var(--ease-out-duration) var(--ease-out-delay) cubic-bezier(0.2, 1, 0.2, 1);
}
.border-right {
transition: var(--ease-in-duration) cubic-bezier(1, 0, 0.8, 0);
}
.border-top {
transition: var(--ease-in-duration) cubic-bezier(1, 0, 0.8, 0);
}
}
&:hover {
color: hsl(var(--hue), 100%, 41%);
background: transparent;
.border-top,
.border-bottom {
transform: scaleX(1);
}
.border-left,
.border-right {
transform: scaleY(1);
}
// when hover, ease-in bottom, left; ease-out top, right
.border-left {
transition: var(--ease-in-duration) cubic-bezier(1, 0, 0.8, 0);
}
.border-bottom {
transition: var(--ease-in-duration) cubic-bezier(1, 0, 0.8, 0);
}
.border-right {
transition: var(--ease-out-duration) var(--ease-out-delay) cubic-bezier(0.2, 1, 0.2, 1);
}
.border-top {
transition: var(--ease-out-duration) var(--ease-out-delay) cubic-bezier(0.2, 1, 0.2, 1);
}
}
}
}
6、伪元素
简而言之,伪元素就是在原先的元素基础上插入额外的元素,而且这个元素不充当HTML的标签,这样就能保持HTML结构的整洁。我们知道每个元素都有::before
和::after
这两个伪元素,也就是说每个元素都提供了3个矩形(元素本身1个,伪元素2个)来供我们进行形状的绘制。现在又有了clip-path
这个属性,几乎任意的形状都可以被绘制出来,全凭你的想象力上面的动图是条子划过文本的动画,条子就是每个文本所对应的伪元素,对每个文本和其伪元素应用动画,就能达到上图的效果了本demo地址:https://codepen.io/alphardex/pen/jOEOEzZ
<header>
<h1 class="title">I'm alphardex.</h1>
<p class="subtitle">A CSS Wizard</p>
</header>
// palette: https://www.materialpalette.com/light-blue/pink
@import url("https://fonts.googleapis.com/css?family=Lato");
@import url("https://fonts.googleapis.com/css?family=Lora:400,400i,700");
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background: #222;
}
header {
.title,
.subtitle {
position: relative;
width: 250px;
height: 30px;
color: transparent;
animation: fadeIn 2s 1.6s forwards;
&::before {
position: absolute;
content: "";
top: 0;
left: 0;
width: 100%;
height: 100%;
transform: scaleX(0);
transform-origin: left;
animation: slideRight 2s cubic-bezier(0.75, 0, 0, 1) forwards;
}
}
.title {
margin: 0;
font-family: Lora, serif;
font-size: 32px;
line-height: 30px;
&::before {
background: #FF4081;
}
}
.subtitle {
margin: 10px 0 0 0;
font-family: Lato, sans-serif;
font-size: 12px;
line-height: 30px;
letter-spacing: 5px;
text-transform: uppercase;
animation-delay: 3.2s;
&::before {
background: #03A9F4;
animation-delay: 2s;
}
}
}
@keyframes fadeIn {
to {
color: white;
}
}
@keyframes slideRight {
50% {
transform: scaleX(1);
transform-origin: left;
}
50.1% {
transform-origin: right;
}
to {
transform-origin: right;
}
}
7、attr()生成文本内容
元素可以有自定义的属性值,它的命名格式通常为data-*``attr()
用于获取元素的这种自定义属性值,并赋值给其伪元素的content
作为其生成的内容利用这个函数,我们可以用伪元素在原先文本的基础上“复制”出另一个文本,如下图所示。
overflow: hidden
,把多余的文本遮住。通过JS分割文本并应用交错动画,就得到了如下的效果,这也是接下来本文要讲的overflow
障眼法。本demo地址:https://codepen.io/alphardex/full/wvBeXjd
<ul class="float-text-menu">
<li><a href="#" data-text="Home">Home</a></li>
<li><a href="#" data-text="Archives">Archives</a></li>
<li><a href="#" data-text="Tags">Tags</a></li>
<li><a href="#" data-text="Categories">Categories</a></li>
<li><a href="#" data-text="About">About</a></li>
</ul>
@import url(https://fonts.googleapis.com/css?family=Lato);
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background: black;
}
.float-text-menu {
display: flex;
flex-direction: column;
list-style-type: none;
li {
a {
display: flex;
padding: 6px;
color: white;
font-family: Lato, sans-serif;
text-decoration: none;
overflow: hidden;
span {
position: relative;
transition: 0.3s;
&::before {
position: absolute;
content: attr(data-text);
transform: translateY(130%);
}
}
&:hover {
span {
transform: translateY(-130%);
}
}
}
}
}
let floatTextMenuLinks = document.querySelectorAll(".float-text-menu li a");
floatTextMenuLinks.forEach(link => {
let letters = link.textContent.split("");
link.textContent = "";
letters.forEach((letter, i) => {
let span = document.createElement("span");
span.textContent = letter;
span.style.transitionDelay = `${i / 20}s`;
span.dataset.text = letter;
link.append(span);
});
});
8、overflow障眼法
之前有做过闪光按钮的效果:鼠标悬浮按钮上时一道光从左到右划过去。笔者就用渐变来模拟那道光,通过transform: translateX()
将其平移至右边。
但这样明显不对啊,这光为啥能被看见呢?不应该把它给“挡”起来吗?于是乎,给按钮加上overflow: hidden
,光在按钮外的位置时就被隐藏起来了。
这就是障眼法的力量:)本demo地址:https://codepen.io/alphardex/pen/eYYzXBZ更多障眼法可以看看这个作品,一次性看个够XD
<button class="btn btn-primary btn-ghost btn-shine">
hover me
</button>
@import url(https://fonts.googleapis.com/css?family=Lato);
body {
display: flex;
height: 100vh;
justify-content: center;
align-items: center;
background: #222;
}
.btn {
--hue: 190;
position: relative;
padding: 1rem 3rem;
font-size: 1rem;
line-height: 1.5;
color: white;
text-decoration: none;
text-transform: uppercase;
background-color: hsl(var(--hue), 100%, 41%);
border: 1px solid hsl(var(--hue), 100%, 41%);
outline: transparent;
overflow: hidden;
cursor: pointer;
user-select: none;
white-space: nowrap;
transition: 0.25s;
&:hover {
background: hsl(var(--hue), 100%, 31%);
}
&-primary {
--hue: 187;
}
&-ghost {
color: hsl(var(--hue), 100%, 41%);
background-color: transparent;
border-color: hsl(var(--hue), 100%, 41%);
&:hover {
color: white;
}
}
&-shine {
color: white;
&::before {
position: absolute;
content: "";
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(
120deg,
transparent,
hsla(var(--hue), 100%, 41%, 0.5),
transparent
);
transform: translateX(-100%);
transition: 0.6s;
}
&:hover {
background: transparent;
box-shadow: 0 0 20px 10px hsla(var(--hue), 100%, 41%, 0.5);
}
&:hover::before {
transform: translateX(100%);
}
}
}
9、兄弟选择符定制表单元素
提示:这里最好将input
作为label
的子元素,这样用户点击label
时就能传到input
上默认的input
太丑怎么办?那就把它先抹掉,用appearance: none
或opacity: 0
都可以然后,利用兄弟选择符~
来定制和input
相邻的所有元素(+
号也行,只不过只能选中最近的元素),例如可以用伪元素生成一个新的方框代替原先的input
,利用伪类:checked
和动画来表示它被勾选后的状态,本质上还是障眼法哦~
CSS特性
善用某些CSS特性,也可以为你的作品增色不少哦
<form>
<fieldset class="todo-list">
<legend class="todo-list__title">My Special Todo List</legend>
<label class="todo-list__label">
<input type="checkbox" name="" id="" />
<i class="check"></i>
<span>Make awesome CSS animation</span>
</label>
<label class="todo-list__label">
<input type="checkbox" name="" id="" />
<i class="check"></i>
<span>Watch awesome bangumi</span>
</label>
<label class="todo-list__label">
<input type="checkbox" name="" id="" />
<i class="check"></i>
<span>Encounter awesome people</span>
</label>
<label class="todo-list__label">
<input type="checkbox" name="" id="" />
<i class="check"></i>
<span>Be an awesome man</span>
</label>
</fieldset>
</form>
// color scheme: https://coolors.co/e63946-585b57-7b9fa1-264456-0b1420
@import url("https://fonts.googleapis.com/css?family=Lato:400,400i,700");
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background: #0b1420;
}
.todo-list {
display: flex;
flex-direction: column;
padding: 0 75px 10px 30px;
background: #162740;
border: transparent;
.todo-list__title {
padding: 3px 6px;
color: #f1faee;
background-color: #264456;
}
.todo-list__label {
display: flex;
align-items: center;
margin: 40px 0;
font-size: 24px;
font-family: Lato, sans-serif;
color: #f1faee;
cursor: pointer;
input[type="checkbox"] {
opacity: 0;
appearance: none;
& + .check {
position: absolute;
width: 25px;
height: 25px;
border: 2px solid #f1faee;
transition: 0.2s;
}
&:checked + .check {
width: 25px;
height: 15px;
border-top: transparent;
border-right: transparent;
transform: rotate(-45deg);
}
& ~ span {
position: relative;
left: 40px;
white-space: nowrap;
transition: 0.5s;
&::before {
position: absolute;
content: "";
top: 50%;
left: 0;
width: 100%;
height: 1px;
background: #f1faee;
transform: scaleX(0);
transform-origin: right;
transition: transform 0.5s;
}
}
&:checked ~ span {
color: #585b57;
&::before {
transform: scaleX(1);
transform-origin: left;
}
}
}
}
}
10、animation
此处包括transition
和transform
CSS动画可以说是利用CSS设计炫酷特效的最强法器,它几乎贯穿了我的所有作品有人问我为什么我能想出这么多的动画?笔者阅番百部,对常用的动画技巧了如指掌,同样那些酷炫的网站只要细心观察,也会给笔者带来很多设计上的灵感。一言以蔽之:只有多欣赏动画,才能写出好的动画。
11、border-radius
为盒子添加圆角,经常用来美化按钮等组件如果设定为50%
则是圆形,也很常用
不规则的曲边形状
调整多个顶点的border-radius
可以做出不规则的曲边形状
本demo地址:https://codepen.io/alphardex/full/abbWOPR
<nav class="navtab">
<ul>
<li class="navtab-item active">
<i class="fas fa-home"></i>
<span>Home</span>
</li>
<li class="navtab-item">
<i class="fas fa-compass"></i>
<span>Explore</span>
</li>
<li class="navtab-item">
<i class="fas fa-bell"></i>
<span>Notification</span>
</li>
<li class="navtab-item">
<i class="fas fa-user"></i>
<span>Profile</span>
</li>
</ul>
</nav>
@import url(https://fonts.googleapis.com/css?family=Lato);
@mixin center {
display: flex;
justify-content: center;
align-items: center;
}
body {
@include center;
height: 100vh;
font-family: Lato, sans-serif;
background: #03a9f4;
}
.navtab {
--navtab-width: 600px;
--navtab-item-width: calc(var(--navtab-width) / 4 - 20px);
--navtab-overlay-width: calc(var(--navtab-item-width) + 80px);
--active-index: 0;
position: relative;
width: var(--navtab-width);
height: 150px;
background: white;
border: 1em solid white;
// https://9elements.github.io/fancy-border-radius/full-control.html#15.5.15.15-50.95.50.85-150.600
border-radius: 5% 5% 15% 15% / 15% 15% 50% 50%;
overflow: hidden;
ul {
@include center;
width: 100%;
height: 100%;
padding: 0;
margin: 0;
list-style-type: none;
li.navtab-item {
@include center;
z-index: 2;
flex-direction: column;
width: var(--navtab-item-width);
height: 100%;
color: #0288d1;
cursor: pointer;
transition: 0.5s ease;
i {
font-size: 2em;
transition: 0.5s ease;
}
span {
font-size: 20px;
user-select: none;
opacity: 0;
transition: 0.5s ease;
}
&.active {
width: var(--navtab-overlay-width);
i {
transform: translateY(-10px);
}
span {
opacity: 1;
}
}
}
}
&::after {
position: absolute;
content: "";
top: 0;
left: 0;
height: 100%;
width: var(--navtab-overlay-width);
background: #b3e5fc;
border-radius: 20px;
transform: translateX(calc(var(--navtab-item-width) * var(--active-index)));
transition: 0.5s ease;
}
}
let navtab = document.querySelector("nav.navtab");
let navtabItems = document.querySelectorAll("li.navtab-item");
navtabItems.forEach((navtabItem, activeIndex) =>
navtabItem.addEventListener("click", () => {
navtabItems.forEach(navtabItem => navtabItem.classList.remove("active"));
navtabItem.classList.add("active");
(navtab as HTMLElement).style.setProperty(
"--active-index",
`${activeIndex}`
);
})
);
12、box-shadow
为盒子添加阴影,增加盒子的立体感,可以多层叠加,并且会使阴影更加丝滑
<ul class="pagination">
<li class="page-prev">
<a class="prev" href="#"><i class="fas fa-arrow-left"></i></a>
</li>
<li class="page-number active"><a href="#">1</a></li>
<li class="page-number"><a href="#">2</a></li>
<li class="page-number"><a href="#">3</a></li>
<li class="page-number"><a href="#">4</a></li>
<li class="page-number"><a href="#">5</a></li>
<li class="page-number"><a href="#">6</a></li>
<li class="page-next">
<a class="next" href="#"><i class="fas fa-arrow-right"></i></a>
</li>
</ul>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background: lightblue;
}
.pagination {
--active-index: 0;
display: flex;
padding: 10px 20px;
background: white;
border-radius: 50px;
box-shadow:
0 0.3px 0.6px rgba(0, 0, 0, 0.056),
0 0.7px 1.3px rgba(0, 0, 0, 0.081),
0 1.3px 2.5px rgba(0, 0, 0, 0.1),
0 2.2px 4.5px rgba(0, 0, 0, 0.119),
0 4.2px 8.4px rgba(0, 0, 0, 0.144),
0 10px 20px rgba(0, 0, 0, 0.2)
;
list-style-type: none;
li {
margin: 0 5px;
&.page-number {
width: 50px;
height: 50px;
line-height: 50px;
text-align: center;
&:hover a {
color: white;
background: #777;
}
&.active a {
color: white;
background: #333;
}
}
&.page-prev,
&.page-next {
font-weight: 700;
}
&.page-prev {
margin-right: 20px;
}
&.page-next {
margin-left: 20px;
}
a {
display: block;
line-height: 50px;
font-size: 20px;
font-weight: 600;
text-decoration: none;
color: #777;
border-radius: 50%;
transition: 0.3s;
&.prev:hover,
&.next:hover {
color: darken(#777, 50%);
}
}
}
}
let prevLink = document.querySelector(".prev");
let nextLink = document.querySelector(".next");
let pagination = document.querySelector(".pagination");
let pageNumberLinks = document.querySelectorAll(".page-number a");
let maxPageIndex = pageNumberLinks.length - 1;
pageNumberLinks.forEach((pageNumberLink, activeIndex) => {
pageNumberLink.addEventListener("click", () => {
pageNumberLinks.forEach(pageNumberLink =>
pageNumberLink.parentElement.classList.remove("active")
);
pageNumberLink.parentElement.classList.add("active");
(pagination as HTMLElement).style.setProperty(
"--active-index",
`${activeIndex}`
);
});
});
prevLink.addEventListener("click", () => {
pageNumberLinks.forEach(pageNumberLink =>
pageNumberLink.parentElement.classList.remove("active")
);
let activeIndex = Number(
(pagination as HTMLElement).style.getPropertyValue("--active-index")
);
activeIndex = activeIndex > 0 ? activeIndex - 1 : 0;
pageNumberLinks[activeIndex].parentElement.classList.add("active");
(pagination as HTMLElement).style.setProperty(
"--active-index",
`${activeIndex}`
);
});
nextLink.addEventListener("click", () => {
pageNumberLinks.forEach(pageNumberLink =>
pageNumberLink.parentElement.classList.remove("active")
);
let activeIndex = Number(
(pagination as HTMLElement).style.getPropertyValue("--active-index")
);
activeIndex = activeIndex < maxPageIndex ? activeIndex + 1 : maxPageIndex;
pageNumberLinks[activeIndex].parentElement.classList.add("active");
(pagination as HTMLElement).style.setProperty(
"--active-index",
`${activeIndex}`
);
});
13、遮罩
如果给box-shadow
的扩张半径设定足够大的值,可以用它来遮住背景,而无需额外的div元素
14、内发光
注意到box-shadow
还有个inset
,用于盒子内部发光利用这个特性我们可以在盒子内部的某个范围内设定颜色,做出一个新月形
本demo地址:https://codepen.io/alphardex/full/eYmGEGp
text-shadow
文本阴影,本质上和box-shadow
相同,只不过是相对于文本而言,常用于文本发光,也可通过多层叠加来制作霓虹文本和伪3D文本等效果
15、发光文本
本demo地址:https://codepen.io/alphardex/full/Exxodoq
16、霓虹文本
本demo地址:https://codepen.io/alphardex/full/rNNwmZz
17、伪3D文本
本demo地址:https://codepen.io/alphardex/full/QWWavvx
18、background-clip:text
能将背景裁剪成文字的前景色,常用来和color: transparent
配合生成渐变文本
本demo地址:https://codepen.io/alphardex/full/QWwveZG
gradient
渐变可以作为背景图片的一种,具有很强的色彩效果,甚至可以用来模拟光
19、linear-gradient
线性渐变是笔者最常用的渐变这个作品用到了HTML的dialog
标签,渐变背景,动画以及overflow
障眼法,细心的你看出来了吗:)本demo地址:https://codepen.io/alphardex/full/eYYxzBm
20、radial-gradient
径向渐变常用于生成圆形背景,上面例子中Snow的背景就是一个椭圆形的径向渐变此外,由于背景可以叠加,我们可以叠加多个不同位置大小的径向渐变来生成圆点群,再加上动画就产生了一种微粒效果,无需多余的div
元素。
本demo地址:https://codepen.io/alphardex/full/OJPvMGx
21、conic-gradient
圆锥渐变可以用于制作饼图用一个伪元素叠在饼图上面,并将content
设为某个值(这个值通过CSS变量计算出来),就能制作出度量计的效果,障眼法又一次完成了它的使命。
本demo地址:https://codepen.io/alphardex/full/BaydVvQ
filter
PS里的滤镜,玩过的都懂,blur
最常用
22、backdrop-filter
对背景应用滤镜,产生毛玻璃的效果本demo地址:https://codepen.io/alphardex/full/pooQMVp
23、mix-blend-mode
PS里的混合模式,常用于文本在背景下的特殊效果以下利用滤色模式(screen
)实现文本视频蒙版效果
本demo地址:https://codepen.io/alphardex/full/wvvLYpV
24、clip-path
PS里的裁切,可以制作各种不规则形状。如果和动画结合也会相当有意思。本demo地址:https://codepen.io/alphardex/full/ZEEBRrq
25、-webkit-box-reflect
投影效果,不怎么常用,适合立体感强的作品。本demo地址:https://codepen.io/alphardex/full/ExaZgxp
26、web animations
虽然这并不是一个CSS特性,但是它经常用于完成那些CSS所做不到的事情那么何时用它呢?当CSS动画中有属性无法从CSS中获取时,自然就会使用到它了
跟踪鼠标的位置
目前CSS还尚未有获取鼠标位置的API,因此考虑用JS来进行通过查阅相关的DOM API,发现在监听鼠标事件的API中,可通过e.clientX
和e.clientY
来获得鼠标当前的位置既然能够获取鼠标的位置,那么跟踪鼠标的位置也就不是什么难事了:通过监听mouseenter
和mouseleave
事件,来获取鼠标出入一个元素时的位置,并用此坐标来当作鼠标的位移距离,监听mousemove
事件,来获取鼠标在元素上移动时的位置,同样地用此坐标来当作鼠标的位移距离,这样一个跟踪鼠标的效果就实现了。
本demo地址:https://codepen.io/alphardex/full/OJPmQGz
CSS Houdini
CSS Houdini是CSS的底层API,它使我们能够通过这套接口来扩展CSS的功能
让渐变动起来
目前来说,我们无法直接给渐变添加动画,因为浏览器不理解要改变的值是什么类型这时,我们就可以利用CSS.registerProperty()
来注册我们的自定义变量,并声明其语法类型(syntax
)为颜色类型<color>
,这样浏览器就能理解并对颜色应用插值方法来进行动画还记得上文提到的圆锥渐变conic-gradient()
吗?既然它可以用来制作饼图,那么我们能不能让饼图动起来呢?答案是肯定的,定义三个变量:--color1
、--color2
和--pos
,其中--pos
的语法类型为长度百分比<length-percentage>
,将其从0
变为100%
,饼图就会顺时针旋转出现。
本demo地址:https://codepen.io/alphardex/full/RwNxpXQ
27、彩蛋
将交错动画和伪类伪元素结合起来写出来的慎重勇者风格的菜单本demo地址:https://codepen.io/alphardex/full/ExavZdV
最后:
恭喜你将本文读完了。不论是过了一场视觉盛宴也好,还是学到了不少东西也好,还是直接从书签那导航到这里也好(笑),CSS的力量始终超乎你的想象。只要敢于创作,你就是这个世界的神。
`