过渡动画
我们可以通过优雅的过渡动画效果进入和离开DOM来吸引客户,svelte通过transition指令让这个问题变得简单起来。
1. 淡入淡出
1.1 生硬显示隐藏
正常我们想要做一个元素的显示与隐藏,可以这样
<script>
let visible = true;
</script>
<main>
<label for="">
<input type="checkbox" bind:checked={visible}> // 开关控制元素是否隐藏
visible {visible}
</label>
{#if visible}
<p>Fade In And Out 看看淡入淡出效果</p>
{/if}
</main>
但实现后我们发现,这个突然出现和突然消失的效果太过生硬了,因此我们可以设置一些动画,看起来更柔和舒服一些,所以我们可以将上述代码做如下更改
1.2 淡入淡出 <div transition:fade>..
import { fade } from 'svelte/transition' // 引入动画库中具体的动画效果
// 在使用的标签上面增加transition:[动画效果]
<p transition:fade>Fade In And Out 看看淡入淡出效果</p>
1.3 接收参数 <div transition:fly={参数}>...
transition函数可以接受参数,我们可以切换一个可以接受参数的动画效果fly。这个fly效果就是在离开的时候,根据设置,向y轴方向移动并慢慢消失,出现时从离开的位置慢慢出现
import { fly } from 'svelte/transition'
<p transition:fly={{y: 100, duration: 2000}}>Fade In And Out 看看淡入淡出效果</p>
1.4 出入动画 in:[动画效果] out:[动画效果]
刚刚我们上述没有区分进行设置,动画在in和out时是可以执行不同的动画效果的,我们可以指定其中的一个或者两个。
- transition:[动画效果] in和out都执行这个动画
- in:[动画效果] 在出现/进入时执行这个动画效果
- out:[动画效果] 在消失/离开时执行这个动画效果
import { fade, fly } from 'svelte/transition'
<p in:fly={{y: 100, duration: 2000}} out:fade>Fade In And Out 看看淡入淡出效果</p>
2. 自定义CSS过渡
我们可以像官方示例一样封装一个动画效果。在动画进入时绑定了一个spin
的效果,由于spin是我们自定义的效果,所以我们需要定一个spin函数,接收两个参数,node当前DOM和options配置。之后在函数中对接收到的参数进行处理,就可以得到我们想要的动画效果啦
<script>
import { fade } from 'svelte/transition'
import { elasticOut } from 'svelte/easing'
let visible = true;
function spin(node, { duration }) { // node 绑定元素
return {
duration,
css: t => {
const eased = elasticOut(t)
return `
transform: scale(${eased}) rotate(${eased * 1080}deg);
color: hsl(
${~~(t * 360)},
${Math.min(100, 1000 - 1000 * t)}%,
${Math.min(50, 500 - 500 * t)}%
)
`
}
}
}
</script>
<main>
<label for="">
<input type="checkbox" bind:checked={visible}>
visible
</label>
{#if visible}
<div class="centered" in:spin="{{duration: 8000}}" out:fade>
<span>transitions!</span>
</div>
{/if}
</main>
<style>
.centered {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
span {
position: absolute;
transform: translate(-50%, -50%);
font-size: 4em;
}
</style>
- css函数:接收一个时间参数,返回的内容是动态的根据t来进行计算的内联样式
- hsl()函数:使用色相、饱和度、亮度来定义颜色,这样随着时间的变化,文字的内容就会各种颜色进行变换达到一个炫酷的效果
- ~~: 相当于Math.floor()
3. 自定义JS过渡
虽然通常我们应该尽可能多的使用CSS进行过渡,但是如果不借用JavaScript,有些效果是无法实现的。以下官网示例:逐字打印
<script>
let visible = false
function typewriter(node, {speed = 50}) {
const valid = (
node.childNodes.length === 1 && node.childNodes[0].nodeType === 3 // 只有一个子节点并且为文本节点
);
if(!valid) {
throw new Error('This transition only works on elements with a single text node child')
}
const text = node.textContent;
const duration = text.length * speed;
return {
duration,
tick: t => {
const i = ~~(text.length * t)
node.textContent = text.slice(0, i)
}
}
}
</script>
<main>
<label for="">
<input type="checkbox" bind:checked={visible}>
visible
</label>
{#if visible}
<p in:typewriter>
The quick brown fox jumps over the lazy dog
</p>
{/if}
</main>
4. 过渡事件
了解过渡的开始和结束会很有用,Svelte调度监听事件,像监听其他任何DOM事件一样
- on:introstart=fn 动画进入开始时执行该函数
- on:introend=fn 动画进入结束时执行该函数
- on:outrostart=fn 动画离开开始时执行该函数
- on:outroend=fn 动画离开结束时执行该函数
<script>
import { fly } from "svelte/transition";
let visible = true;
let status = 'waiting'
</script>
<main>
<p>status: {status}</p>
<label for="">
<input type="checkbox" bind:checked={visible}>visible
</label>
{#if visible}
<p
transition:fly={{ y: 188, duration: 2000}}
on:introstart="{() => status = 'intro start'}"
on:introend="{() => status = 'intro end'}"
on:outrostart="{() => status = 'outro start'}"
on:outroend="{() => status = 'outro end'}"
>
动画效果进出
</p>
{/if}
</main>
5. 局部过渡
无论是添加或销毁任何标签容器,过渡动画都会在标签上进行播放。单个列表项的过渡效果影响到切换整个列表,以致切换可见性时也有过渡效果。如果我们只是想在列表项新增或删除时出现过渡效果,在显示和隐藏时不使用动画效果,可以通过局部(local)过渡来实现该功能。
<script>
import { slide } from 'svelte/transition'
let showItems = true
let i = 5
let items = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten']
</script>
<main>
<label for="">
<input type="checkbox" name="" id="" bind:checked={showItems}> 展示列表
</label>
<label for="">
<input type="range" bind:value={i} max=10>
</label>
{#if showItems}
{#each items.slice(0, i) as item}
<div transition:slide|local>
{item}
</div>
{/each}
{/if}
</main>
<style>
div {
padding: 0.5em 0;
border-top: 1px solid #eee;
}
</style>
6. 延时过渡
svelte过渡引擎其中一项特别强大的功能就是可以设置延时过渡,以便多个效果之间协调。
- 引入函数,并定义动画函数
import { quintOut } from 'svelte/easing'; import { crossfade } from 'svelte/transition' const [send, receive] = crossfade({...
- 在标签中绑定动画效果
in:receive="{{key: todo.id}}" out:send="{{key: todo.id}}"
<script>
import { quintOut } from 'svelte/easing';
import { crossfade } from 'svelte/transition'
const [send, receive] = crossfade({
duration: d => Math.sqrt(d * 200),
fallback(node, params) {
const style = getComputedStyle(node)
const transform = style.transform === 'none' ? '' : style.transform
return {
duration: 600,
easing: quintOut,
css: t => `
transform: ${transform} scale(${t});
opacity: ${t}
`
}
}
})
let uid = 1
let todos = [
{ id: uid++, done: false, description: 'write some docs' },
{ id: uid++, done: false, description: 'start writing blog post' },
{ id: uid++, done: true, description: 'buy some milk' },
{ id: uid++, done: false, description: 'mow the lawn' },
{ id: uid++, done: false, description: 'feed the turtle' },
{ id: uid++, done: false, description: 'fix some bugs' },
];
function add(input) {
const todo = {
id: uid++,
done: false,
description: input.value
}
todos = [todo, ...todos]
input.value = ''
}
function mark(todo, done) {
todo.done = done
remove(todo)
todos = todos.concat(todo)
}
function remove(todo) {
todos = todos.filter(t => t !== todo)
}
</script>
<main>
<div class="board">
<input type="text" placeholder="请输入你的待办事项" on:keydown={e => e.which === 13 && add(e.target)}>
<div class="left">
<h2>待办项</h2>
{#each todos.filter(t => !t.done) as todo (todo.id)}
<label for="" in:receive="{{key: todo.id}}" out:send="{{key: todo.id}}">
<input type="checkbox" on:change={() => mark(todo, true)}>
{todo.description}
<button on:click="{() => remove(todo)}">remove</button>
</label>
{/each}
</div>
<div class="right">
<h2>已办</h2>
{#each todos.filter(t => t.done) as todo (todo.id)}
<label for="" class="done" in:receive="{{key: todo.id}}" out:send="{{key: todo.id}}">
<input type="checkbox" checked on:change={() => mark(todo, false)}>
{todo.description}
<button on:click="{() => remove(todo)}">remove</button>
</label>
{/each}
</div>
</div>
</main>
<style>
.board {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 1em;
max-width: 36em;
margin: 0 auto;
}
.board > input {
font-size: 1.4em;
grid-column: 1/3;
}
h2 {
font-size: 2em;
font-weight: 200;
user-select: none;
margin: 0 0 0.5em 0;
}
label {
position: relative;
line-height: 1.2;
padding: 0.5em 2.5em 0.5em 2em;
margin: 0 0 0.5em 0;
border-radius: 2px;
user-select: none;
border: 1px solid hsl(240, 8%, 70%);
background-color:hsl(240, 8%, 93%);
color: #333;
}
input[type="checkbox"] {
position: absolute;
left: 0.5em;
top: 0.6em;
margin: 0;
}
.done {
border: 1px solid hsl(240, 8%, 90%);
background-color:hsl(240, 8%, 98%);
}
button {
position: absolute;
top: 0;
right: 0.2em;
width: 2em;
height: 100%;
background: no-repeat 50% 50% url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23676778' d='M12,2C17.53,2 22,6.47 22,12C22,17.53 17.53,22 12,22C6.47,22 2,17.53 2,12C2,6.47 6.47,2 12,2M17,7H14.5L13.5,6H10.5L9.5,7H7V9H17V7M9,18H15A1,1 0 0,0 16,17V10H8V17A1,1 0 0,0 9,18Z'%3E%3C/path%3E%3C/svg%3E");
background-size: 1.4em 1.4em;
border: none;
opacity: 0;
transition: opacity 0.2s;
text-indent: -9999px;
cursor: pointer;
}
label:hover button {
opacity: 1;
}
</style>
7. 示例中新知识
7.1 左移运算符 <<
- 2 << 3
- 左边的2转成二进制 10
- 左移3位后变为 10000
- 转成十进制是16
7.2 user-select 是否能选取元素的文本
在web浏览器中,如果您在文本上双击或者鼠标左键滑动,文本会被选取或高亮显示。user-select属性用于阻止这种行为。
- user-select:none; CSS写法
- object.style.userSelect=“none”; JS写法
- user-select可选值:
- auto: 默认。如果浏览器允许,则可以选择文本
- none:防止文本可被用户选取
- text:文本可被用户选取
- all:单击选取文本,而不是双击
7.3 text-indent
首行文本缩进
7.4 Math.sqrt
返回一个数的平方根
- Math.sqrt(4) -> 2
7.5 css中hsl函数
HSL 即色相、饱和度、亮度(英语:Hue, Saturation, Lightness)。
- 色相(H)是色彩的基本属性,就是平常所说的颜色名称,如红色、黄色等。 定义色相 (0 到 360) - 0 (或 360) 为红色, 120 为绿色, 240 为蓝色
- 饱和度(S)是指色彩的纯度,越高色彩越纯,低则逐渐变灰,取 0-100% 的数值。定义饱和度; 0% 为灰色, 100% 全色
- 亮度(L),取 0-100%,增加亮度,颜色会向白色变化;减少亮度,颜色会向黑色变化。定义亮度 0% 为暗, 50% 为普通, 100% 为白
7.6 display: grid;网格布局
7.6.1 网格布局简介
-
Grid布局将网页划分成一个个网格,可以任意组合不同的网格,做出各种各样的布局。与Flex布局有一定的相似性,都是可以指定容器内部多个项目的位置。但是它们之间也存在差异。
-
Flex布局是轴线布局,只能指定"项目"针对轴线的位置,可以看作是一维布局。Grid布局则是将容器划分成行和列,产生单元格,然后指定项目所在的单元格,可以看作是二维布局。Grid布局远比Flex布局强大。
-
display: grid; // 指定容器采用网格布局
-
默认情况下容器元素都是块级圆度,也可以设成行内元素
-
display: inline-grid;
-
设为网格布局以后,容器子元素(项目)的
float
display:inline-block
display:table-cell
vertical-align
和columb-*
等设置都将失效。 -
容器,网格的最外层元素。项目,容器的顶级子元素
7.6.2 容器属性-列宽行高grid-template-columns/grid-template-rows
容器指定了网格布局以后,接着就要划分行和列
- grid-template-columns: 100px 200px 80px 20px; // 每一列的列宽,同时容器被自动划分成对应数据个数列。
- grid-template-rows: grid-template-rows: 60px 80px 60px; // 每一行的行高,若共计四行,只设置三个行高值,则第四个元素默认初始行高。
- 上述列宽和行高,除了使用绝对单位px,也可使用百分比方式。
7.6.2.1 设置行高列宽时可用函数
- repeat(): 有时重复写同样的值非常麻烦,尤其是网格较多时,此时可使用repeat函数,简化重复某个值或某种模式(重复多个值)。
- repeat(num, a b c …) 将后面a b c设置的值重复num次
- grid-template-columns: repeat(4, 100px 200px); // 八列,交替100 200
- grid-template-rows: repeat(2, 33%); // 两行,分别占总高的33%
- auto-fill: 有时单元格的大小是固定的,但是容器的大小不确定。如果希望每一行或每一列容纳尽可能多的单元格,此时可使用auto-fill关键词表示自动填充。
- grid-template-columns: repeat(auto-fill, 100px); 均匀单元格,一行尽可能放更多的列
- fr 片段:用于表示比例关系,如果两列的宽度分别为1fr和2fr,就表示后者是前者的两倍。
- grid-template-columns: 1fr 2fr 3fr 1fr; // 将一行分开,四列按比例排放
- grid-template-columns: 1fr 800px 3fr 1fr; // 将一行分开,第二列800宽,剩余宽度按比例排放
- minmax(): 产生一个长度范围,表示长度就在这个范围之中。
- grid-template-columns: 1fr 1fr minmax(100px, 2fr) 3fr; // 表示该列最小宽度100px,最大宽度 2fr
- auto: 表示由浏览器自己决定长度
- grid-template-columns: 100px auto 200px; // 除非设置min-width大于最大宽度,否则第二列基本等于该单元格额最大宽度。
7.6.2.2 网格线的名称
grid-template-columns 属性和 grid-template-rows属性里面还可以使用方括号,指定每一根网格线的名字,方便后续的引用
grid-template-columns: [c1] 100px [c2] 120px [c3] auto [c4];
grid-template-rows: [r1] 100px [r2] 100px [r3] auto [r4];
上述代码指定网格布局为3行 X 3列,所以有4根垂直网格线和4根水平网格线。[]内为这八根线的名字。网格布局中同一根线可以有很多个名字,比如[fifth-line row5]
7.6.3 容器属性-行/列间距 row-gap/column-gap/gap
- grid-row-gap: 10px; // 行间距 行与行之间存在,不像margin,需要将last-child设为0
- grid-column-gap: 8px; // 列间距 列与列之间存在,同上行
- grid-gap: 10px 20px; // 行间距和列间距合并。前面行间距 后面列间距。若省略了第二个值,则默认第二个值等于第一个值。
根据最新标准,上面三个属性名的grid-前缀已经删除,grid-column-gap和grid-row-gap写成column-gap和row-gap,grid-gap写成gap。
7.6.4 容器属性-grid-template-area定义区域
网格布局允许指定“区域”,一个区域由单个或多个单元格组成。这个区域可以配合class属性、grid-area一起指定某个区域应该展现什么样式
<main>
<!-- container对应的这个div为该网格的容器,下面的每一个project为对应的项目。 -->
<!-- 网格根据对应的行和列生成一个又一个的单元格 -->
<div class="container">
<div class="project a">A</div>
<div class="project a">B</div>
<div class="project a">C</div>
<div class="project b">D</div>
<div class="project b">E</div>
<div class="project b">F</div>
<div class="project c">G</div>
<div class="project c">H</div>
<div class="project c">I</div>
</div>
</main>
<style>
.container {
display: grid;
grid-template-columns: repeat(3, 100px);
grid-template-rows: repeat(4, 100px);
grid-column-gap: 10px;
gap: 10px 20px;
grid-template-areas: 'a a a'
'b b b'
'c c c';
}
.a {
grid-area: a;
background: antiquewhite;
}
.b {
grid-area: b;
background: blue;
}
.c {
grid-area: c;
background: blueviolet;
}
.project {
width: 100%;
text-align: center;
border: 1px solid #d1d1d1;
}
</style>
我们将对应的area设置成a b c可能语义化并不是很明确,也可以这样设置
grid-template-areas: "header header header"
"main main sidebar"
"footer footer footer";
grid-template-areas: 'a . c'
'd . f'
'g . i';
中间列为点,表示没有用到该单元格,或者该单元格不属于任何区域。
注意,区域的命名会影响到网格线。每个区域的起始网格线,会自动命名为区域名-start,终止网格线自动命名为区域名-end。比如,区域名为header,则起始位置的水平网格线和垂直网格线叫做header-start,终止位置的水平网格线和垂直网格线叫做header-end。
7.6.5 容器属性-grid-auto-flow 内容放置顺序
划分网格后,容器的子元素会按照顺序,自动放置在每一个网格。默认的放置顺序是“先行后列”,也可以通过该属性的设置,先列后行。
- grid-auto-flow: row; // 默认情况,先行后列排放,即一行一行放,此时根据列宽位置的设置,决定几列,然后将内容换算成对应的行。
- grid-auto-flow: column; // 先列后行排放,一列一列放,此时根据设置行高的数量决定一共有多少行,然后将内容放置对应行数后生成对应的列
- grid-auto-flow: column dense;
- grid-auto-flow: row dense;
7.6.6 容器属性-justify-items/align-items/place-items单元格内容放置位置
align-items 设置单元格内容的垂直位置(上中下)
justify-items 设置单元格内容的水平位置(左中右)
place-items: <align-items> <justify-items>
若不设置第二个值,默认与第一个值相等
- start: 对齐单元格的起始边缘
- end:对齐单元格的结束边缘
- center:单元格内部居中
- stretch:拉伸,占满整个单元格的整个宽度
7.6.7 容器属性-justify-content/align-content/place-content整个内容区域在容器里面的位置
整个内容区域 -> 指的是表格形成区域
容器 -> 指的是container这个容器,若设置宽高800X800 但表格区域不一定会占满
justify-content 整个内容区域在容器里面的水平位置
align-content 整个内容区域在容器垂直位置
place-content:<align-content><justify-content>
省略第二个值等于第一个值
- start 对齐容器的起始边框
- end 对齐容器的结束边框
- center 容器内部居中
- stretch 项目大小没有指定是,拉伸占据整个网格容器
- space-around 每个项目两侧的间隔相等。所以项目之间的间隔比容器边框的间隔大一倍。与flex中的这个属性定义相同
- space-between 项目与项目之间的间隔相等,项目与容器边框之间没有间隔
- space-evenly 项目与项目之间的间隔相等,项目与容器边框之间也是同样长度的间隔
7.6.8 容器属性-grid-auto-columns/grid-auto-rows超出指定网格的样式
例如我们定义了3X3网格,那么在第四行或第四列时样式就会变成默认的,此时我们可以通过这两个属性进行定制化
- grid-auto-rows: 100px;
- grid-auto-columns: 200px;
7.6.9 容器属性-grid-template/grid合并属性
- grid-template 是grid-template-columns、grid-template-rows和grid-template-area这三个属性的合并简写形式
- grid是grid-template-rows、grid-template、grid-template-area、grid-auto-rows、grid-auto-columns、grid-auto-flow这六个属性合并的简写形式
7.6.10 项目属性-grid-column-start/grid-column-end/grid-row-start/grid-row-end 指定项目位置
给某个具体的项目设置这几个值,可以指定为第几条网格线,还可以指定为网格线的名字
- 指定第几条网格线
.first-project { // class为first-project的项目
grid-column-start: 2; // 列 第二条竖线 // 两条竖线合并处算一条
grid-column-end: 4; // 列 第四条竖线
grid-row-start: 3; // 行 第三条横线 // 两条横线合并出算一条
grid-row-end: 4; // 行 第四条横线
}
- 指定网格线的名字
.container {
display: grid;
grid-template-columns: [a] 100px [b] 200px [c]300px [d];
grid-template-rows: [a1] 200px [b1] 100px [c1] 50px [d1];
}
.first-project {
grid-column-start: a;
grid-column-end: d;
grid-row-start: b1;
grid-row-end: c1;
}
使用这四个属性,如果产生了项目的重叠,则可以使用z-index属性指定项目的重叠顺序。
7.6.11 项目属性-grid-column/grid-row
- grid-column 是grid-column-start和grid-column-end合并简写
- grid-row 是 grid-row-start和gird-row-end合并简写形式
7.6.12 项目属性-grid-area 执行项目放在哪一个区域
- grid-area: e; // 放在某个具体的位置
- grid-area:
<row-start> / <column-start> / <row-end> / <column-end>
;
7.6.13 justify-self/align-self/place-self单元格内容位置
justify-self 设置单元格内容的水平位置
align-self 设置单元格内容的垂直位置
place-self: <align-self> <justify-self>
; // 若只写一个值,则默认第二个值与第一个相等
- start 对齐单元格的起始边缘
- end 对齐单元格的结束边缘
- center 单元格内部居中
- stretch 拉伸,占满整个单元格的默认宽度
官方文档
如果有用,点个赞呗~
总结用法,希望可以帮助到你,
我是Ably,你无须超越谁,只要超越昨天的自己就好~