整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

分享一款不错的中文布局CSS库—chinese-layout

上篇文章分享了一款史上最没用的中文版的CSS渐变色库,今天再来分享一篇它的兄弟库chinese-layout。

chinese-layout是一款利用CSS自定义属性来制作的中文布局CSS库,由于grid布局十分强大,强大到甚至只需要两行CSS代码就可以完成一个基本的布局。不过grid属性较多较为复杂,并且不支持IE浏览器等特点导致了许多人并不很了解它。


不过近些年来随着IE浏览器的逐步退出市场,兼容性已经不再是特别需要纠结的一件事情了:

可以看到各大浏览器的支持情况已经较为乐观了,为了让大家快速体验grid布局的强大之处,chinese-layout就此诞生!


该库的尺寸十分轻量级,只有不到1KB的大小(653字节),并且使用起来也是十分的方便。


使用方式

假设现在有一个ul元素,我们想要它变为一个九宫格的布局,首先需要在ul元素上写:

ul {
    /* 这个是固定写法,必须先指定display为grid */
    display: grid;
  
  /* grid属性需要指定哪种布局 */
  grid: var(--九宫格);
  
  /* 加入一点间距,让九个元素相互之间有一定的距离 */
  gap: 10px;
}

然后在父元素里面添加九个元素:

<ul>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
</ul>

这就完事啦:

当然这里其实是省略了一些父元素上的宽高以及子元素的颜色等细节。

这些细节就交给你们来决定,在此案例中chinese-layout只负责将父元素分割成九等分。


本文采用了chinese-gradient来作为背景色

安装

要记得先引入这个库才能够去正常使用。如果你的项目是一个工程化的项目,那么:

npm i chinese-layout


记得要在主文件中引入:

import 'chinese-layout'


同时也支持sassless等,如:

import 'chinese-layout/chinese-layout.scss'


如果你只是想在<link>标签里引入的话,那么:

<link rel="stylesheet" href="https://unpkg.zhimg.com/chinese-layout">


全部布局


声明 display: grid;

首先一定要记得声明 display: grid;

因为chinese-layout底层依赖的就是grid布局。


居中

grid: var(--居中)


DOM结构:

<parent>
  <child/>
</parent>

DOM结构只是示意,真实布局时要把parent和child换成你想要的元素,如:<ul> <li></li> </ul>


这种布局需要先在父元素上写这么两行代码:

parent {
  display: grid;
  grid: var(--居中);
}

然后再在子元素上写上:

child {
  grid-area: 中;
} 

但是看起来除了灰蒙蒙一片的背景好像啥也没有是吧,那是因为我们没给子元素加上宽高,而且子元素里也没有任何的内容,导致子元素宽高为0不可见,那么现在我们来给子元素一个宽高再来看看:

child {
  width: 20px;
  height: 20px;
  grid-area: 中;
} 

子元素便会在父元素里水平垂直居中:


双列

grid: var(--双列)

DOM结构:

parent {
  display: grid;
  grid: var(--双列);
}

DOM结构只是示意,真实布局时要把parent和child换成你想要的元素,如:<ul> <li></li> </ul>


来看看语法:

parent {
  display: grid;
  grid: var(--双列);
}

可以看到现在两列紧紧的贴合在一起了,不过有时候这两列我们并不想它们紧贴在一起,这时我们可以使用 gap 属性来控制列与列之间的间距:

parent {
  display: grid;
  grid: var(--双列);
  gap: 10px;
}

运行结果:

可以看到列与列直接距离变成我们设置的10px了,但 gap 属性只会改变列与列之间的距离,并不会改变与父元素之间的距离,所以之前紧贴着父元素的现在还是紧贴着父元素,如果想与父元素有间距的话可以给父元素加padding:

双列布局是不需要指定子元素的 grid-area 属性的,不过你要是非要想指定一下的话也不是不可以:

child1 {
    grid-area: 左;
}

child2 {
  grid-area: 右;
}

运行结果:

(可以通过指定grid-area来颠倒DOM位置,没事可以去试试)


三列

grid: var(--三列)


咦?这不是双列吗?说好的三列呢?

其实是这样,三列中的最中间一列被做成了自适应了,如果不给宽度并且也没有任何带宽度的子元素的话宽度就会为0,所以就看不到最中间那列啦!


那咱们给中间的DOM元素一个宽度吧:

<parent>
  <child1/>
  <child2/>
  <child3/>
</parent>

DOM结构只是示意,真实布局时要把parent和child换成你想要的元素,如:<ul> <li></li> </ul>

parent {
    display: grid;
  grid: var(--三列);
}
child2 {
    width: 800px;
}

运行结果:

也可以不给宽度,直接用内容去撑开:

<parent>
  <child1/>
  <child2>child2</child2>
  <child3/>
</parent>

运行结果:


也同样可以用gap属性来控制间距:

parent {
    display: grid;
  grid: var(--三列);
  gap: 10px;
}
child2 {
    width: 800px;
}

运行结果:


三列布局是不需要指定子元素的 grid-area 属性的,不过你要是非要想指定一下的话也不是不可以:

child1 {
    grid-area: 左;
}

child2 {
  grid-area: 中;
}

child2 {
  grid-area: 右;
}

运行结果:

(可以通过指定子元素的 grid-area 属性来颠倒DOM位置,没事可以去试试)


吕形

grid: var(--吕形)


可是这看起来也不像吕形啊,吕不是应该上面一个口下面一个口吗?

其实还是那个原理:上面的盒子如果不给高度的话默认为0。


那咱们给个高度再看看:

<parent>
  <child1/>
  <child2/>
</parent>

DOM结构只是示意,真实布局时要把parent和child换成你想要的元素,如:<ul> <li></li> </ul>

parent {
    display: grid;
  grid: var(--吕形);
}
child1 {
    height: 100px;
}
child2 {
    overflow-y: auto;
}

运行结果:


还可以通过 gap 属性来控制间距:

parent {
    display: grid;
  grid: var(--吕形);
  gap: 10px;
}
child1 {
    height: 100px;
}

运行结果:


也可以通过指定子元素的 grid-area 属性来颠倒DOM位置:

parent {
    display: grid;
  grid: var(--吕形);
  gap: 10px;
}
child1 {
  grid-area: 下;
  overflow-y: auto;
}
child2 {
  height: 100px;
  grid-area: 上;
}

运行结果:


上下栏

grid: var(--上下栏)


看过前面几种布局的朋友应该猜到了,是因为上盒子和下盒子没给高度导致现在只能看见中间那栏,咱们给个高度再来看看:

<parent>
  <child1/>
  <child2/>
  <child3/>
</parent>

DOM结构只是示意,真实布局时要把parent和child换成你想要的元素,如:<ul> <li></li> </ul>

parent {
    display: grid;
  grid: var(--上下栏);
}
child1 {
    height: 80px;
}
child2 {
  overflow-y: auto;
}
child3 {
  height: 100px;
}

运行结果:


还可以通过 gap 属性来控制间距:

parent {
    display: grid;
  grid: var(--上下栏);
  gap: 10px;
}
child1 {
    height: 80px;
}
child2 {
  overflow-y: auto;
}
child3 {
  height: 100px;
}

运行结果:


也可以通过指定子元素的 grid-area 属性来颠倒DOM位置:

parent {
    display: grid;
  grid: var(--上下栏);
  gap: 10px;
}
child1 {
  grid-area: 中;
  overflow-y: auto;
}
child2 {
  height: 80px;
  grid-area: 上;
}
child3 {
  height: 100px;
  grid-area: 下;
}

运行结果:


四宫格

grid: var(--四宫格)


DOM结构:

<parent>
  <child1/>
  <child2/>
  <child3/>
  <child4/>
<parent/>

DOM结构只是示意,真实布局时要把parent和child换成你想要的元素,如:<ul> <li></li> </ul>

parent {
  display: grid;
  grid: var(--四宫格);
}

如果不想各个宫格紧贴在一起,可以用 gap 属性来控制间距:

parent {
  display: grid;
  grid: var(--四宫格);
  gap: 10px;
}

运行结果:

gap 属性是控制行和列之间间距的,但如果你想要行间距和列间距不一样的话,就要用到下面两个属性了:

  • row-gap:行与行之间的间距
  • column-gap:列与列之间的间距


也可以通过 grid-area 属性来重新分配DOM元素的位置:

child1 { grid-area: 左上; }
child2 { grid-area: 右上; }
child3 { grid-area: 左下; }
child4 { grid-area: 右下; }

运行结果:


六宫格

grid: var(--六宫格)


DOM结构:

<parent>
  <child1/>
  <child2/>
  <child3/>
  <child4/>
  <child5/>
  <child6/>
<parent/>

DOM结构只是示意,真实布局时要把parent和child换成你想要的元素,如:<ul> <li></li> </ul>

parent {
  display: grid;
  grid: var(--六宫格);
}

如果不想各个宫格紧贴在一起,可以用 gap 属性来控制间距:

parent {
  display: grid;
  grid: var(--六宫格);
  gap: 10px;
}

运行结果:

gap 属性是控制行和列之间间距的,但如果你想要行间距和列间距不一样的话,就要用到下面两个属性了:

  • row-gap:行与行之间的间距
  • column-gap:列与列之间的间距


也可以通过 grid-area 属性来重新分配DOM元素的位置:

child1 { grid-area: 左上; }
child2 { grid-area: 右上; }
child3 { grid-area: 左下; }
child4 { grid-area: 右下; }

运行结果:


九宫格

grid: var(--九宫格)


DOM结构:

<parent>
  <child1/>
  <child2/>
  <child3/>
  <child4/>
  <child5/>
  <child6/>
  <child7/>
  <child8/>
  <child9/>
<parent/>

DOM结构只是示意,真实布局时要把parent和child换成你想要的元素,如:<ul> <li></li> </ul>

parent {
  display: grid;
  grid: var(--九宫格);
}

如果不想各个宫格紧贴在一起,可以用 gap 属性来控制间距:

parent {
  display: grid;
  grid: var(--九宫格);
  gap: 10px;
}

运行结果:

gap 属性是控制行和列之间间距的,但如果你想要行间距和列间距不一样的话,就要用到下面两个属性了:

  • row-gap:行与行之间的间距
  • column-gap:列与列之间的间距


也可以通过 grid-area 属性来重新分配DOM元素的位置:

child1 { grid-area: 左上; }
child2 { grid-area: 中上; }
child3 { grid-area: 右上; }
child4 { grid-area: 左中; }
child5 { grid-area: 中中; }
child6 { grid-area: 右中; }
child7 { grid-area: 左下; }
child8 { grid-area: 中下; }
child9 { grid-area: 右下; }

运行结果:


铺满

grid: var(--铺满)


看起来貌似啥也没有,那是因为顾名思义,铺满就是子元素和父元素的大小一样大嘛,来看看DOM结构:

<parent>
  <child/>
</parent>

DOM结构只是示意,真实布局时要把parent和child换成你想要的元素,如:<ul> <li></li> </ul>

parent {
  display: grid;
  gird: var(--铺满);
}


圣杯

grid: var(--圣杯)


DOM结构:

<parent>
  <child1/>
  <child2/>
  <child3/>
  <child4/>
  <child5/>
</parent>

DOM结构只是示意,真实布局时要把parent和child换成你想要的元素,如:<ul> <li></li> </ul>

圣杯布局的子元素必须指定位置

parent {
  display: grid;
  grid: var(--圣杯);
}
child1 { grid-area: 上; }
child2 { grid-area: 左; }
child3 { grid-area: 中; }
child4 { grid-area: 右; }
child5 { grid-area: 下; }

但是看起来并没有看到圣杯布局的影子,灰蒙蒙的一片,还是那个原因,把上下左右的宽高控制权都留给用户,如果子元素里没有任何内容并且没有指定宽高的话就不会显示在屏幕上,我们来给个合适的宽高再看看:

parent {
  display: grid;
  grid: var(--圣杯);
}
child1 {
  height: 80px;
  grid-area: 上;
}
child2 {
  width: 100px;
  grid-area: 左;
}
child3 { grid-area: 中; }
child4 {
  width: 100px;
  grid-area: 右;
}
child5 {
  height: 80px;
  grid-area: 下;
}

运行结果:

再给个间距看看效果:

parent {
  display: grid;
  grid: var(--圣杯);
  gap: 10px;
}

运行结果:

不给宽高只靠子元素的内容撑起来也可以,而且可以缺少某一个DOM元素,比如我们不想要"右"了:

<parent>
  <child1/>
  <child2/>
  <child3/>
  <child4/>
</parent>

DOM结构只是示意,真实布局时要把parent和child换成你想要的元素,如:<ul> <li></li> </ul>

parent {
  display: grid;
  grid: var(--圣杯);
  gap: 10px;
}
child1 {
  height: 80px;
  grid-area: 上;
}
child2 {
  width: 100px;
  grid-area: 左;
}
child3 { grid-area: 中; }
child4 {
  height: 80px;
  grid-area: 下;
}

运行结果:

虽然看起来"中"的右侧受 gap 属性的影响导致有一定的间距,但我们可以不加 gap,靠 margin 来控制间距:

parent > child {
  margin: 10px;
}

运行结果:

即使不给"中"设置宽高,"中"也会根据父元素的宽高来自动调整自己的宽高大小。

也可以靠 row-gap column-gap 属性来单独控制横、纵间距:


双飞翼

grid: var(--双飞翼)


DOM结构:

<parent>
  <child1/>
  <child2/>
  <child3/>
  <child4/>
  <child5/>
</parent>

DOM结构只是示意,真实布局时要把parent和child换成你想要的元素,如:<ul> <li></li> </ul>

双飞翼布局的子元素必须指定位置

parent {
  display: grid;
  grid: var(--双飞翼);
}
child1 { grid-area: 上; }
child2 { grid-area: 左; }
child3 { grid-area: 中; }
child4 { grid-area: 右; }
child5 { grid-area: 下; }

但是看起来并没有看到双飞翼布局的影子,灰蒙蒙的一片,还是那个原因,把上下左右的宽高控制权都留给用户,如果子元素里没有任何内容并且没有指定宽高的话就不会显示在屏幕上,我们来给个合适的宽高再看看:

parent {
  display: grid;
  grid: var(--双飞翼);
}
child1 {
  height: 80px;
  grid-area: 上;
}
child2 {
  width: 100px;
  grid-area: 左;
}
child3 { grid-area: 中; }
child4 {
  width: 100px;
  grid-area: 右;
}
child5 {
  height: 80px;
  grid-area: 下;
}

运行结果:

再给个间距看看效果:

parent {
  display: grid;
  grid: var(--双飞翼);
  gap: 10px;
}

运行结果:

不给宽高只靠子元素的内容撑起来也可以,而且可以缺少某一个DOM元素,比如我们不想要"右"了:

<parent>
  <child1/>
  <child2/>
  <child3/>
  <child4/>
</parent>

DOM结构只是示意,真实布局时要把parent和child换成你想要的元素,如:<ul> <li></li> </ul>

parent {
  display: grid;
  grid: var(--双飞翼);
  gap: 10px;
}
child1 {
  height: 80px;
  grid-area: 上;
}
child2 {
  width: 100px;
  grid-area: 左;
}
child3 { grid-area: 中; }
child4 {
  height: 80px;
  grid-area: 下;
}

运行结果:

虽然看起来"中"的右侧受 gap 属性的影响导致有一定的间距,但我们可以不加 gap ,靠 margin 来控制间距:

parent > child {
  margin: 10px;
}

运行结果:

即使不给"中"设置宽高,"中"也会根据父元素的宽高来自动调整自己的宽高大小。

也可以靠 row-gap column-gap 属性来单独控制横、纵间距:


响应式

grid: var(--响应式)


响应式布局会根据父元素的大小以及子元素的数量来自行决定如何排版


DOM结构:

<parent>
  <child1/>
    <child2/>
   ......
    <child-N/>
</parent>

DOM结构只是示意,真实布局时要把parent和child换成你想要的元素,如:<ul> <li></li> </ul>

parent {
  display: grid;
  grid: var(--响应式);
}

不过每列的最小宽度默认是100px,如果想改变这一大小的话需要在 :root 选择器上定义一个 --宽 变量,比如想要宽变成30px:

:root {
  --宽: 30px;
}

如果每个子元素之间的距离太近了的话别忘记用 gap 属性来控制间距哦:

parent {
  display: grid;
  grid: var(--响应式);
  gap: 10px;
}

总结

整体来说这款插件还是不错的,使用简单方便,中文命名虽然符合中国人的习惯,但命名太多比较依赖文档。

还在为布局头疼的朋友可以快速入手哦!

击右上方红色按钮关注“web秀”,让你真正秀起来

前言

以前说页面是动态,基本都是说数据是从数据库查询的,不是写死在html代码里面的。现在的说页面动态,一般会问:你是说数据,还是效果动态?

CSS3 Grid布局实现Loading动画效果

好的前端动画效果,能给用户带来非常舒适的体验效果,更甚者,有用户觉的你这个动画效果非常nice,反复操作,就为看你这个动画。停留时间,预览量上了,带来的收益也不是一丁点吧。

当然也不用为了动画,而额外的来制作动画效果。比如一个弹框,可以直接渐变出现的,你还加了飞了一圈出现,那就是不必要的动画了。

所以恰大好处的动画效果,能带来非常不错的效果。

下面我们来学习如果只做一些简单的动画效果:

CSS3 Grid布局实现Loading动画效果

grid布局

CSS3 Grid布局实现Loading动画效果

上图的动画,就是一个简单的loading加载效果,而且是一个3x3的九宫格。是因为旋转才变成一个菱形的样子。我们先来画出3x3的九宫格:

html

<div class="loading">
 <span></span>
 <span></span>
 <span></span>
 <span></span>
 <span></span>
 <span></span>
 <span></span>
 <span></span>
 <span></span>
</div>

这里用9个span来做每个格子元素。

css

body {
 margin: 0;
 height: 100vh; /*=100%*/
 display: flex; /*flex布局*/
 align-items: center; /*flex布局:垂直居中*/
 justify-content: center; /*flex布局:水平居中*/
 background-color: black;
}
.loading {
 width: 10em;
 height: 10em;
 display: grid; /*grid布局*/
 grid-template-columns: repeat(3, 1fr);
 grid-gap: 0.5em; /*grid 每个item之间的间距*/
}
/**
* --name 是css中定义变量的方式
* 可以直接用 var(--name) 使用
*/
.loading span {
 background-color: var(--color); /*背景颜色*/
}
.loading span:nth-child(2n+2) {
 /*n=0: 2*/
 /*n=1: 4*/
 /*n=2: 6*/
 /*n=3: 8*/
 /*n=4: 10(不存在)*/
 --color: #f13f84;
}
.loading span:nth-child(4n+3) {
 /*n=0: 3*/
 /*n=1: 7*/
 /*n=2: 11(不存在)*/
 --color: #46b0ff;
}
.loading span:nth-child(4n+1) {
 /*n=0: 1*/
 /*n=1: 5*/
 /*n=2: 9*/
 /*n=3: 13(不存在)*/
 --color: #44bb44;
}

CSS3 Grid布局实现Loading动画效果

grid-template-columns: 该属性是基于 网格列. 的维度,去定义网格线的名称和网格轨道的尺寸大小。

repeat: 表示网格轨道的重复部分,以一种更简洁的方式去表示大量而且重复列的表达式。

有了九宫格布局后,我们直接旋转这个loading元素,制作动画。

CSS3动画

.loading {
 ...
 transform: rotate(45deg); /*旋转45°*/
}
.loading span {
 background-color: var(--color);
 /**
 * 动画名字是blinking
 * 动画整个时间是2s
 * 每个元素的执行延时时间 var(--delay)
 * 动画的速度曲线 由慢到快 ease-in-out
 * 永久执行 infinite
 */
 animation: blinking 2s var(--delay) ease-in-out infinite;
 animation-fill-mode: backwards;
}
/**
* 每个元素执行动画延时时间变量
*/
.loading span:nth-child(7) {
 --delay: 0s;
}
.loading span:nth-child(4n+4) {
 --delay: 0.2s;
}
.loading span:nth-child(4n+1) {
 --delay: 0.4s;
}
.loading span:nth-child(4n+2) {
 --delay: 0.6s;
}
.loading span:nth-child(3) {
 --delay: 0.8s;
}
/**
* 动画效果
*/
@keyframes blinking {
 0%, 20% {
 transform: rotate(0deg) scale(0);
 }
 40%, 80% {
 /*
 * 旋转一圈rotate(1turn)[转、圈(Turns)。一个圆共1圈]
 * 缩放 scale 如果大于1就代表放大;如果小于1就代表缩小
 */
 transform: rotate(1turn) scale(1);
 }
 100% {
 transform: rotate(2turn) scale(0);
 }
}

animation语法

animation: name duration timing-function delay iteration-count direction;

1、animation-name 规定需要绑定到选择器的 keyframe 名称。

2、animation-duration 规定完成动画所花费的时间,以秒或毫秒计。

3、animation-timing-function 规定动画的速度曲线。

4、animation-delay 规定在动画开始之前的延迟。

5、animation-iteration-count 规定动画应该播放的次数。

6、animation-direction 规定是否应该轮流反向播放动画。

CSS3 Grid布局实现Loading动画效果

动画的速度曲线

1、linear 规定以相同速度开始至结束的过渡效果(等于 cubic-bezier(0,0,1,1))。(匀速)

2、ease 规定慢速开始,然后变快,然后慢速结束的过渡效果(cubic-bezier(0.25,0.1,0.25,1))(相对于匀速,中间快,两头慢)。

3、ease-in 规定以慢速开始的过渡效果(等于 cubic-bezier(0.42,0,1,1))(相对于匀速,开始的时候慢,之后快)。

4、ease-out 规定以慢速结束的过渡效果(等于 cubic-bezier(0,0,0.58,1))(相对于匀速,开始时快,结束时候间慢,)。

5、ease-in-out 规定以慢速开始和结束的过渡效果(等于 cubic-bezier(0.42,0,0.58,1))(相对于匀速,(开始和结束都慢)两头慢)。

6、cubic-bezier(n,n,n,n) 在 cubic-bezier 函数中定义自己的值。可能的值是 0 至 1 之间的数值。

CSS3 Grid布局实现Loading动画效果

总结

CSS3动画基础知识可以看看 《如何快速上手基础的CSS3动画》 这篇文章,里面用更小的示例,讲述了CSS3动画的每个属性。CSS3动画,无外乎就是animation、transform、transition等属性的使用,记住他们每个的作用特效就可以了。


喜欢小编或者觉得小编文章对你有帮助的,可以点击一波关注哦!

我们就来学点有意思的,用几十行代码来实现一个高性能的抽奖小游戏.也基于此,来巩固我们的javascript基础,以及前端一些基本算法的应用.

效果展示



你将收获

  • 防抖函数的应用
  • 用css实现九宫格布局
  • 生成n维环形坐标的算法
  • 如何实现环形随机轨道运动函数
  • 实现加速度动画
  • 性能分析与优化

设计思路



具体实现

由于目前已有很多方案可以实现九宫格抽奖动画,比如使用动态active实现边框动画,用随机算法和定时器设置在何处停止等等. 为了进一步提高性能,本文介绍的方法,将使用坐标法,将操作dom的成本降低,完全由js实现滑块的路径的计算,滑块元素采用绝对定位,让其脱离文档流,避免其他元素的重绘等等,最后点击按钮我们会使用防抖函数来避免频繁执行函数,造成不必要的性能损失.

1. 九宫格布局实现

为了让大家更加熟悉dom结构,这里我就不用js动态生成了.如下html结构:

<div class="wrap">
    <div class="title">圣诞抽抽乐</div>
    <div class="box">
        <div class="item">我爱你</div>
        <div class="item">你爱我</div>
        <div class="item">我不爱你</div>
        <div class="item">你爱我</div>
        <div class="item start">开始</div>
        <div class="item">你爱我</div>
        <div class="item">再见</div>
        <div class="item">谢谢惠顾</div>
        <div class="item">你爱我</div>
        <div class="spin"></div>
    </div>
</div>
复制代码

九宫格布局我们使用flex来实现,核心代码如下:

.box {
    display: flex;
    flex-wrap: wrap;
    width: 300px;
    height: 300px;
    position: relative;
    .item {
        box-sizing: border-box;
        width: 100px;
    }
    // 滑块
    .spin {
        box-sizing: border-box;
        position: absolute;
        left: 0;
        top: 0;
        display: inline-block;
        width: 100px;
        height: 100px;
        background-color: rgba(0,0,0,.2);
    }
}
复制代码

由上可知容器box采用flex布局,要想让flex子元素换行,我们这里要设置flex-wrap: wrap;此时九宫格布局就实现了. 滑块采用绝对定位,至于具体如何去沿着环形轨道运动,请继续看下文介绍.

2.生成n维环形坐标的算法


由上图我们可以知道,一个九宫格的4条边,可以用以上8个坐标收尾连接起来,那么我们可以基于这个规律.来生成环形坐标集合.代码如下:


/**
 * 生成n维环形坐标
 * @param {number} n 维度
 * @param {number} cell 单位坐标长度
 */
function generateCirclePath(n, cell) {
  let arr = []
  for(let i=0; i< n; i++) {
      arr.push([i*cell, 0])
  }
  for(let i=0; i< n-1; i++) {
      arr.push([(n-1)*cell, (i+1)*cell])
  }
  for(let i=0; i< n-1; i++) {
      arr.push([(n-i-2)*cell, (n-1)*cell])
  }
  for(let i=0; i< n-2; i++) {
      arr.push([0, (n-i-2)*cell])
  }
  return arr
}
复制代码

如果是单位坐标,那么cell为1,cell设计的目的就位为了和现实的元素相结合,我们可以手动设置单元格的宽度来实现不同大小的n维环形坐标集.

3.实现环形随机轨道运动函数

由抽奖动画分析可知,我们滑块运动的轨迹,其实就是环形坐标集合,所以我们只要让滑块的顶点(默认左上角)沿着环形坐标集合一步步变化就好了.

function run(el, path, n = 1, i = 0, len = path.length) {
    setTimeout(() => {
        if(n > 0) {
          if(len <= i) {
              i = n === 1 ? len : 0
              n--
          }
          el.css('transform', `translate(${path[i][0]}px, ${path[i][1]}px)`)
          run(el, path, n, ++i, len)
        }
    }, 300)   
}
复制代码

这样就能实现我们的滑块按照九宫格边框运动的动画了,当然以上函数只是基本的动画, 还没有实现在随机位置停止, 以及滑块的加速度运动,这块需要一定的技巧和js基础知识比如闭包.

3.1 加速度运动

加速度运动其实很简单,比如每转过一圈将setTimeout的延迟时间改变即可.代码如下:

function run(el, path, n = 1, speed = 60, i = 0, len = path.length) {
    setTimeout(() => {
        if(n > 0) {
          if(len <= i) {
              i = n === 1 ? len : 0
              n--
              speed += (300 - speed) / n
          }
          el.css('transform', `translate(${path[i][0]}px, ${path[i][1]}px)`)
          run(el, path, n, speed, ++i, len)
        }
    }, speed)   
}
复制代码

3.2 随机停止实现

随机停止这块主要是用了Math.random这个API, 我们在最后一圈的时候, 根据随机返回的数值来决定何时停止,这里我们在函数内部实现随机数值,完整代码如下:

/**
* 环形随机轨道运动函数
* @param {element} el 运动的dom元素
* @param {array} path 运动的环形坐标集合
* @param {number} speed 运动的初始速度
* @param {number} i 运动的初始位置
* @param {number} len 路径的长度
* @param {number} random 中奖坐标
*/
function run(el, path, n = 1, speed = 60, i = 0, len = path.length, random = Math.floor(Math.random() * len)) {
    setTimeout(() => {
        if(n > 0) {
          // 如果n为1,则设置中奖数值
          if(n === 1) {
            len = random
          }
          if(len <= i) {
              i = n === 1 ? len : 0
              n--
              speed += (300 - speed) / n
          }
          el.css('transform', `translate(${path[i][0]}px, ${path[i][1]}px)`)
          run(el, path, n, speed, ++i, len, random)
        }
    }, speed)   
}
复制代码

4.实现点击开始的防抖函数以及应用

防抖函数实现:

// 防抖函数,避免频繁点击执行多次函数
function debounce(fn, interval = 300) {
  let timeout = null
  return function () {
      clearTimeout(timeout)
      timeout = setTimeout(() => {
          fn.apply(this, arguments)
      }, interval)
  }
}
复制代码

那么我们点击时,代码应该长这样:

// 点击开始按钮,开始抽奖
$('.start').on('click',debounce(() => { run($('.spin'), generateCirclePath(3, 100), 3) }))
复制代码

延伸

在文章发布之后,有热心的小伙伴们提出了几个建议,综合如下:

  • 抽奖动画结束后提供回调来通知页面以便处理其他逻辑
  • 处理多次点击时,虽然加了防抖,但是用户在动画没结束时点击了开始按钮,又会执行动画导致动画越来越快,发生混乱.

综合以上问题,我在之前基础上做了进一步扩展,来解决以上提到的问题.

  1. 添加动画结束时回调:
/**
* 环形随机轨道运动函数
* @param {element} el 运动的dom元素
* @param {array} path 运动的环形坐标集合
* @param {func} cb 动画结束时回调
* @param {number} speed 运动的初始速度
* @param {number} i 运动的初始位置
* @param {number} len 路径的长度
* @param {number} random 中奖坐标
*/
function run(el, path, n = 1, cb, speed = 60, i = 0, len = path.length, random = Math.floor(Math.random() * len)) {
    setTimeout(() => {
        if(n > 0) {
          // 如果n为1,则设置中奖数值
          if(n === 1) {
            len = random
          }
          if(len <= i) {
              i = n === 1 ? len : 0
              n--
              speed += (300 - speed) / n
          }
          el.css('transform', `translate(${path[i][0]}px, ${path[i][1]}px)`)
          run(el, path, n, cb, speed, ++i, len, random)
        }else {
          cb && cb()
        }
    }, speed)
}
复制代码
  1. 处理多次点击时,虽然加了防抖,但是用户在动画没结束时点击了开始按钮,又会执行动画导致动画越来越快,发生混乱.
// 1. 点击开始按钮,开始抽奖
$('.start').on('click',debounce(() => {
    // 点击开始后禁用点击
    $('.start').css('pointer-events', 'none')
    run($('.spin'), generateCirclePath(3, 100), 3, () => {
      // 动画结束后开启按钮点击
      $('.start').css('pointer-events', 'auto')
      alert('抽奖结束')
    }) 
}))
复制代码

谢谢各位认真的建议,继续优化吧.

总结

该实现方式的好处是支持n维环形坐标的抽奖,基于坐标法的应用还有很多,尤其是游戏和图形领域,在实现过程中一定要考虑性能和可扩展性,这样我们就可以在不同场景使用同一套方法论,岂不乐哉?本文完整源码我会放在github上,欢迎交流学习~

github地址:https://github.com/MrXujiang?tab=repositories

欢迎在公众号《趣谈前端》加入我们一起学习讨论,共同探索前端的边界。