3D 方块

如何在 CSS 中创建立体的方块呢?用以下的 SCSS mixin 即可方块的长度、高度、深度都可以通过 CSS 变量自由调节

@mixin cube($width, $height, $depth) {

  &__front {

    @include cube-front($width, $height, $depth);

  }

  &__back {

    @include cube-back($width, $height, $depth);

  }

  &__right {

    @include cube-right($width, $height, $depth);

  }

  &__left {

    @include cube-left($width, $height, $depth);

  }

  &__top {

    @include cube-top($width, $height, $depth);

  }

  &__bottom {

    @include cube-bottom($width, $height, $depth);

  }

  .face {

    position: absolute;

  }

}

@mixin cube-front($width, $height, $depth) {

  width: var($width);

  height: var($height);

  transform-origin: bottom left;

  transform: rotateX(-90deg) translateZ(calc(calc(var(#{$depth}) * 2) - var(#{$height})));

}

@mixin cube-back($width, $height, $depth) {

  width: var($width);

  height: var($height);

  transform-origin: top left;

  transform: rotateX(-90deg) rotateY(180deg) translateX(calc(var(#{$width}) * -1)) translateY(

      calc(var(#{$height}) * -1)

    );

}

@mixin cube-right($width, $height, $depth) {

  width: calc(var(#{$depth}) * 2);

  height: var($height);

  transform-origin: top left;

  transform: rotateY(90deg) rotateZ(-90deg) translateZ(var(#{$width})) translateX(calc(var(#{$depth}) * -2)) translateY(calc(var(

            #{$height}

          ) * -1));

}

@mixin cube-left($width, $height, $depth) {

  width: calc(var(#{$depth}) * 2);

  height: var($height);

  transform-origin: top left;

  transform: rotateY(-90deg) rotateZ(90deg) translateY(calc(var(#{$height}) * -1));

}

@mixin cube-top($width, $height, $depth) {

  width: var($width);

  height: calc(var(#{$depth}) * 2);

  transform-origin: top left;

  transform: translateZ(var($height));

}

@mixin cube-bottom($width, $height, $depth) {

  width: var($width);

  height: calc(var(#{$depth}) * 2);

  transform-origin: top left;

  transform: rotateY(180deg) translateX(calc(var(#{$width}) * -1));

}

.cube {

  --cube-width: 3rem;

  --cube-height: 3rem;

  --cube-depth: 1.5rem;

  @include cube(--cube-width, --cube-height, --cube-depth);

  width: 3rem;

  height: 3rem;

}

交错旋转

给多个方块应用交错动画会产生如下效果

.spiral-tower {

  display: grid;

  grid-auto-flow: row;

  transform: rotateX(-30deg) rotateY(45deg);

  .cube {

    @for $i from 1 through 48 {

      &:nth-child(#{$i}) {

        animation-delay: 0.015s * ($i - 1);

      }

    }

  }

}

@keyframes spin {

  0%,

  15% {

    transform: rotateY(0);

  }

  85%,

  100% {

    transform: rotateY(1turn);

  }

}

本 demo 地址:Spiral Tower[1]

伸缩长度

在 CSS 动画中,我们无法直接使变量动起来(其实能动,但很生硬)这时我们就得求助于 CSS Houdini,将变量声明为长度单位类型即可,因为长度单位是可以动起来的

CSS.registerProperty({

  name: "--cube-width",

  syntax: "<length>",

  initialValue: 0,

  inherits: true,

});

CSS.registerProperty({

  name: "--cube-height",

  syntax: "<length>",

  initialValue: 0,

  inherits: true,

});

CSS.registerProperty({

  name: "--cube-depth",

  syntax: "<length>",

  initialValue: 0,

  inherits: true,

});

本 demo 地址:3D Stair Loading[2]

文本分割

在上一篇我们提到了如何用 JS 来分割文本,本篇将介绍一种更简洁的实现方法——gsap 的 SplitText 插件,利用它我们能用更少的代码来实现下图的效果

<div class="staggered-land-in font-bold text-2xl">Fushigi no Monogatari</div>

const t1 = gsap.timeline();

const staggeredLandInText = new SplitText(".staggered-land-in", {

  type: "chars",

});

t1.from(staggeredLandInText.chars, {

  duration: 0.8,

  opacity: 0,

  y: "-20%",

  stagger: 0.05,

});

简化版 demo 地址:SplitText Starter[3]

关键帧

简单的动画固然可以实现,那么相对复杂一点的动画呢?这时候还是要依靠强大的@keyframes 和 CSS 变量注:尽管 gsap 目前也支持 keyframes,但是无法和交错动画结合起来,因此用@keyframes 作为替代方案

<div class="staggered-scale-in font-bold text-6xl">Never Never Give Up</div>

.scale-in-bounce {

  animation: scale-in-bounce 0.4s both;

  animation-delay: calc(0.1s * var(--i));

}

@keyframes scale-in-bounce {

  0% {

    opacity: 0;

    transform: scale(2);

  }

  40% {

    opacity: 1;

    transform: scale(0.8);

  }

  100% {

    opacity: 1;

    transform: scale(1);

  }

}

const t1 = gsap.timeline();

const staggeredScaleInText = new SplitText(".staggered-scale-in", {

  type: "chars",

});

const staggeredScaleInChars = staggeredScaleInText.chars;

staggeredScaleInChars.forEach((item, i) => {

  item.style.setProperty("--i", `${i}`);

});

t1.to(staggeredScaleInChars, {

  className: "scale-in-bounce",

});

本 demo 地址:Staggered Scale In Text[4]

SVG 滤镜

CSS 的滤镜其实都是 SVG 滤镜的封装版本,方便我们使用而已SVG 滤镜则更加灵活强大,以下是几个常见的滤镜使用场景附在线调试 SVG 滤镜的网站:SVG Filters[5]

粘滞效果

<svg width="0" height="0" class="absolute">

  <filter id="goo">

    <feGaussianBlur stdDeviation="10 10" in="SourceGraphic" result="blur" />

    <feColorMatrix

      type="matrix"

      values="1 0 0 0 0

    0 1 0 0 0

    0 0 1 0 0

    0 0 0 18 -7"

      in="blur"

      result="colormatrix"

    />

    <feComposite in="SourceGraphic" in2="colormatrix" operator="over" result="composite" />

  </filter>

</svg>

.gooey {

  filter: url("#goo");

}

本 demo 地址:SVG Filter Gooey Menu[6]

故障效果

<svg width="0" height="0" class="absolute">

  <filter id="glitch">

    <feTurbulence type="fractalNoise" baseFrequency="0.00001 0.000001" numOctaves="1" result="turbulence1">

      <animate

        attributeName="baseFrequency"

        from="0.00001 0.000001"

        to="0.00001 0.4"

        dur="0.4s"

        id="glitch1"

        fill="freeze"

        repeatCount="indefinite"

      ></animate>

      <animate

        attributeName="baseFrequency"

        from="0.00001 0.4"

        to="0.00001 0.2"

        dur="0.2s"

        begin="glitch1.end"

        fill="freeze"

        repeatCount="indefinite"

      ></animate>

    </feTurbulence>

    <feDisplacementMap

      in="SourceGraphic"

      in2="turbulence1"

      scale="30"

      xChannelSelector="R"

      yChannelSelector="G"

      result="displacementMap"

    />

  </filter>

</svg>

.glitch {

  filter: url("#glitch");

}

本 demo 地址:SVG Filter Glitch Button[7]

动态模糊

CSS 滤镜的 blur 是全方位模糊,而 SVG 滤镜的 blur 可以控制单方向的模糊

<svg width="0" height="0" class="absolute">

  <filter id="motion-blur" filterUnits="userSpaceOnUse">

    <feGaussianBlur stdDeviation="100 0" in="SourceGraphic" result="blur">

      <animate dur="0.6s" attributeName="stdDeviation" from="100 0" to="0 0" fill="freeze"></animate>

    </feGaussianBlur>

  </filter>

</svg>

.motion-blur {

  filter: url("#motion-blur");

}

本 demo 地址:SVG Filter Motion Blur[8]

mask 遮罩

有时候我们想做出一种过渡式的半透明效果,类似下图这样的这时候就得借助 mask 属性了,因为与 mask 生成的渐变的 transparent 的重叠部分会变透明

.divider-grad-mask {

  background: linear-gradient(90deg, var(--blue-color) 0 50%, transparent 0 100%) 0 0 / 2rem 1rem;

  mask: linear-gradient(-90deg, black, transparent);

}

demo 地址:Gradient Mask Divider[9]和 clip-path 结合也会相当有意思,如下图所示的加载特效demo 地址:Mask Loader[10]

CSS 变量

鼠标跟踪

上篇提到了利用 Web Animations API 实现鼠标悬浮跟踪的效果,但其实 CSS 变量也能实现,而且更加简洁高效在 CSS 中定义 x 和 y 变量,然后在 JS 中监听鼠标移动事件并获取鼠标坐标,更新对应的 x 和 y 变量即可

:root {

  --mouse-x: 0;

  --mouse-y: 0;

}

.target {

  transform: translate(var(--mouse-x), var(--mouse-y));

}

let mouseX = 0;

let mouseY = 0;

let x = 0;

let y = 0;

let offset = 50; // center

let windowWidth = window.innerWidth;

let windowHeight = window.innerHeight;

const percentage = (value, total) => (value / total) * 100;

window.addEventListener("mousemove", (e) => {

  mouseX = e.clientX;

  mouseY = e.clientY;

  x = percentage(mouseX, windowWidth) - offset;

  y = percentage(mouseY, windowHeight) - offset;

  document.documentElement.style.setProperty("--mouse-x", `${x}%`);

  document.documentElement.style.setProperty("--mouse-y", `${y}%`);

});

window.addEventListener("resize", () => {

  windowWidth = window.innerWidth;

  windowHeight = window.innerHeight;

});

简化版地址:Mousemove Starter[11]

残影效果

如果将鼠标跟踪和交错动画结合起来,再加点模糊滤镜,就能创作出帅气的残影效果本 demo 地址:Motion Table - Delay[12]

分割

为了做出一个碎片运动相关的动画,或者是一个拼图游戏,我们就要对一张进行分割,且块数、大小等都能随意控制,这时CSS变量就能发挥它的用场了

.puzzle {

  --puzzle-width: 16rem;

  --puzzle-height: 24rem;

  --puzzle-row: 3;

  --puzzle-col: 4;

  --puzzle-gap: 1px;

  --puzzle-frag-width: calc(var(--puzzle-width) / var(--puzzle-col));

  --puzzle-frag-height: calc(var(--puzzle-height) / var(--puzzle-row));

  --puzzle-img: url(...);

  display: flex;

  flex-wrap: wrap;

  width: calc(var(--puzzle-width) + calc(var(--puzzle-col) * var(--puzzle-gap) * 2));

  height: calc(var(--puzzle-height) + calc(var(--puzzle-row) * var(--puzzle-gap) * 2));

  .fragment {

    --x-offset: calc(var(--x) * var(--puzzle-frag-width) * -1);

    --y-offset: calc(var(--y) * var(--puzzle-frag-height) * -1);

    width: var(--puzzle-frag-width);

    height: var(--puzzle-frag-height);

    margin: var(--puzzle-gap);

    background: var(--puzzle-img) var(--x-offset) var(--y-offset) / var(--puzzle-width) var(--puzzle-height) no-repeat;

  }

}

设定好分割的行列,根据行列来动态计算切片的大小

拼图的总宽|高=拼图宽|高+列|行数 * 间隙 * 2

切片的显示利用背景定位的xy轴偏移,偏移量的计算方式:x|y坐标 * 切片宽|高 * -1

在JS中,设定好变量值并动态生成切片的xy坐标,即可完成的分割

class Puzzle {

  constructor(el, width = 16, height = 24, row = 3, col = 3, gap = 1) {

    this.el = el;

    this.fragments = el.children;

    this.width = width;

    this.height = height;

    this.row = row;

    this.col = col;

    this.gap = gap;

  }

  create() {

    this.ids = [...Array(this.row * this.col).keys()];

    const puzzle = this.el;

    const fragments = this.fragments;

    if (fragments.length) {

      Array.from(fragments).forEach((item) => item.remove());

    }

    puzzle.style.setProperty("--puzzle-width", this.width + "rem");

    puzzle.style.setProperty("--puzzle-height", this.height + "rem");

    puzzle.style.setProperty("--puzzle-row", this.row);

    puzzle.style.setProperty("--puzzle-col", this.col);

    puzzle.style.setProperty("--puzzle-gap", this.gap + "px");

    for (let i = 0; i < this.row; i++) {

      for (let j = 0; j < this.col; j++) {

        const fragment = document.createElement("div");

        fragment.className = "fragment";

        fragment.style.setProperty("--x", j);

        fragment.style.setProperty("--y", i);

        fragment.style.setProperty("--i", j + i * this.col);

        puzzle.appendChild(fragment);

      }

    }

  }

}

const puzzle = new Puzzle(document.querySelector(".puzzle"));

本demo地址:Split Image With CSS Variable[13]

复杂动画

案例1

本demo地址:Elastic Love[14]

案例2

本demo地址:Infinite Line Animation[15]

案例3

本demo地址:Orbit Reverse[16]

案例4

本demo地址:Motion Table - Solid Rotation[17]

案例5

本demo地址:Motion Table - Symmetric Move[18]

小结

以上几个复杂的动画或多或少都有以下的特征:

div很多,对布局的要求很高

@keyframes很多,对动画的要求很高

有的动画有较多的3d变换

案例5的教程已经写在之前的博文“画物语——CSS动画之美[19]”里了,其余案例亦可以用此文提到的方法进行研究笔者的CSS动画作品全放在这个集合里了:CSS Animation Collection[20]

彩蛋

螺旋阶梯动画(灵感来自灰色的果实OP)

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

热门产品

php编程基础教程.pptx|php编程培训,php,编程,基础,教程,pptx
php编程基础教程.pptx

历史上的今天:04月20日

ThinkPHP5快速入门基础

ThinkPHP5快速入门基础一、基础快速入门 ( 一 ) :基础本章介绍了 ThinkPHP5 .0 的安装及基本使用 ,并给出了一个最简单的示例带你了解如何开始开发 ,主要包 含 :简介官网下载 omposer安装和更新CGit下载和更新目录结构运行环境入口文件调试模式控制器视图读取数据总结在学习 ThinkPHP5.0 之前 ,如果你还不理解面向对象和命名空间的概念 ,建议首先去PHP手册恶

ThinkPHP5快速入门

ThinkPHP5快速入门目 录零、序言一、基础二、URL和路由三、请求和响应四、数据库五、查询语言六、模型和关联 (1)模型定义 (2)基础操作 (3)读取器和修改器 (4)类型转换和自动完成 (5)查询范围 (6)输入和验证 (7)关联 (8)模型输出七、视图和模板八、调试和日志九、API开发十、命令行工具十一、扩展十二、杂项SessionCookie验证

热门专题

安徽中源管业有限公司|安徽中源管业有限公司,安徽中源管业有限公司介绍,安徽中源管业有限公司电话,安徽中源管业有限公司地址,安徽中源管业有限公司厂家,安徽中源管业有限公司电力管,安徽中源管业有限公司管材
安徽中源管业有限公司
开放大学|开放大学报名,开放大学报考,开放大学,什么是开放大学,开放大学学历,开放大学学费,开放大学报名条件,开放大学报名时间,开放大学学历,开放大学专业
开放大学
卓越综合高中|卓越综合高中
卓越综合高中
弥勒综合高中|弥勒综合高中
弥勒综合高中
APP开发|app开发_app开发公司_app软件开发_专业app开发_云南app开发公司_app定制_原生app开发定制
APP开发
自考本科|自考本科有用吗,自考文凭,自考本科文凭,自考文凭有用吗,自考本科文凭有用吗,自考文凭承认吗
自考本科
昆明网站建设|昆明网站建设,昆明网站开发,昆明网站建设公司,昆明网站建设价格,昆明网站设计,昆明网站制作,网页设计,高端网站建设,高端网站设计
昆明网站建设
安徽开放大学|安徽开放大学报名,安徽开放大学报考,安徽开放大学,什么是安徽开放大学,安徽开放大学学历,安徽开放大学学费,安徽开放大学报名条件,安徽开放大学报名时间,安徽开放大学学历,安徽开放大学专业
安徽开放大学

微信小程序

微信扫一扫体验

立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部