css3动画、canvas、svg
前端可视化技术
- 底层图形引擎:Skia(c++,2/2.5d),OpenGL(c,更适合画3d)等
- W3C提供:
CSS3、Canvas(基于Skia)、SVG(基于Skia)、WebGL(基于OpenGL) - 第三方可视化库:Zrender、
Echarts、AntV、Highcharts、D3.js、Three.js、高德地图、百度地图 - 低代码可视化平台:阿里云(DataV)、腾讯云图、网易有数
css动画
2D-transform
- 平移
translate(x,y) - 缩放
sclae(x,y) - 旋转
rotate(deg) - 倾斜
skew(deg)
元素坐标系
- css中的每个元素都有一个坐标系,位于元素左上角(初始坐标系)
- transform,默认
transform-origin: 50%, 50%,坐标系原点默认移到元素中心 - transform,旋转或倾斜会改变坐标系,不同顺序也会导致不同转换结果
3D-transform
- 3d形变函数会创建一个合成层来启动GPU硬件加速
- 平移
translate3d(x,y,z) - 缩放
sclae3d(x,y,z) - 旋转
rotate3d(x,y,z,a) translateX(x)===transalte3d(x,0,0)scaleX(n)===scale3d(n,1,1),n为倍数rotateX(50deg)===rotate3d(1,0,0,50deg)
3D透视 - perspective
perspective: d,d:观察者与z=0平面的距离(眼睛的位置),是具有三维的元素产生透视效果- 观察者、物体、z=0平面,三者之间的关系。
3D空间-transform-style
- transform-style,在父元素中设置,控制子元素位于2D平面还是在3D空间中
transform-style: flat子元素位于2D平面transform-style: preserve-3d子元素位于3D平面
3D背面可见性- backface-visibility
- 当某个元素背向观察者时是否可见
baceface-visibility: visible,可见(默认)baceface-visibility: hidden,不可见
例子-正方体
<style>
body {
padding: 100px;
}
.box {
width: 100px;
height: 100px;
background-color: rgb(0, 255, 255);
position: relative;
transform-style: preserve-3d;
transform: rotateX(-33.5deg) rotateY(45deg);
backface-visibility: hidden;
}
.child {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.child.top {
background-color: rgba(230, 217, 47, 0.6);
transform: rotateX(90deg) translateZ(50px);
}
.child.bottom {
background-color: rgb(230, 217, 47, 0.6);
transform: rotateX(90deg) translateZ(-50px);
}
.child.left {
background-color: rgba(78, 245, 106, 0.5);
transform: translateZ(-50px);
}
.child.right {
background-color: rgba(78, 245, 106, 0.5);
transform: translateZ(50px);
}
.child.front {
background-color: rgba(219, 112, 233, 0.5);
transform: rotateY(90deg) translateZ(-50px);
}
.child.behind {
background-color: rgb(219, 112, 233, 0.5);
transform: rotateY(90deg) translateZ(50px);
}
</style>
<body>
<div class="box">
<div class="child top">1</div>
<div class="child bottom">2</div>
<div class="child left">3</div>
<div class="child right">4</div>
<div class="child behind">5</div>
<div class="child front">6</div>
</div>
</body>
两个cube旋转动画
浏览器渲染路程
- 解析HTML,构建 DOM tree
- 解析CSS文件,构建规则树
- DOM tree + 规则树 生成 render tree
- 布局layout:计算每个节点的宽度、高度、位置信息。元素大小、位置变化导致需要重新计算布局,成为回流
- 绘制paint:可见元素绘制在屏幕中
- 标准流在同一层绘制
- 一些特殊属性会创建新的层
- 不影响布局的css修改导致重绘,回流必然重绘
- 合成层composite:一些特殊属性会创建新的合成层,并可以利用GPU来加速绘制,但是以消耗内存为代价,不能滥用
css3动画性能优化
- 创建新渲染层(减少回流)
- 有明确的定位属性(display: relative/fixed/sticky/absolute)
- 透明度(opacity 小于1)
- transform
- 对opacity、transform、filter 应用动画等等
- 创建合成层
- 对opacity、transform、filter 应用动画
- 3D transfrom
- will-change: opacity/transfrom/top/left/bottom/right
Canvas
- 应用场景:动画、游戏画面、数据可视化、图片编辑、实时视频处理等
- 优点
- 适合像素级处理,动态渲染、大量数据绘制
- 游戏开发,频繁重绘大量图形对象
- 能以png和jpg格式保存图像,对图片进行像素级处理
- 缺点:canvas占用内存;canvas只能通过js操作;canvas是像素点构成的图形,放大会模糊
<canvas></canvas>默认单位px- 操作:获取canvas元素,调用canvas对应api进行绘制
window.onload = function() {
// 获取canvas元素
let canvasEl = document.getElementById("canvasImage")
if(!canvasEl.getContext) return;
// ctx 绘图上下文,提供了很多绘图api
let ctx = canvasEl.getContext('2d') // 2d | webgl
}
canvas grid(坐标空间)
- 网格也可以理解为坐标空间,原点默认为左上角,一个空格为1px
绘制矩形 rectangle
canvas有两种方式绘制矩形
- 矩形方法
- fillRect(x,y,width,height)
- strokeRect(x,y,width,height)
- clearRect(x,y,width,height)
- 点和路径
路径-绘制
- 创建路径起始点
ctx.beginPath() - 使用绘图命令去画路径
- 移动画笔
ctx.moveTo(x,y)设置绘画起点 - 绘制直线
ctx.lineTo(x,y)设置直线终点 - 绘制圆弧或圆
ctx.arc(x,y,radius,startAngle,endAngle,anticlockwise)- anticlockwise,逆时针方向,默认false(顺时针)
- 弧度(radian):一个圆的弧度为
2*ΠR / R = 2Πrad
- 移动画笔
- 把路径闭合,不是必须
ctx.closePath() - 路径生成后,通过描边或者填充渲染图形
ctx.stroke()ctx.fill()
- 上色,在描边或填充前
ctx.fillStyle = 'orange'ctx.strokeStyle = 'blue'
线型 line style
lineWidth = valuelineCap = type- butt 截断,默认
- round 圆形
- squre 正方形
lineJoin = type- round 圆形
- bevel 斜角
- miter 默认
文本
- 样式
ctx.font = value,10px sanc-serif(默认)ctx.textAlign = value,start(默认)、end、left、right、centerctx.textBaselinr = value,top,hanging,middle,alphabetic,ideographic,bottom
- 绘制
ctx.fillText(text,x,y[,maxWidth])ctx.strokeText(text,x,y[,maxWidth])
图片
- drawImage
drawIamge(iamge,x,y)drawIamge(iamge,x,y,width,height)drawIamge(iamge,sx,sy,sWidth,sHeight,dx,dy,dWidth,dHeight)- 前4个为图像源的切片位置和大小,后4个为切片目标显示位置和大小
- 图片来源
- HTMLImageElement:
Image()或者<img> - HTMLVideoElement:
<video>,从视频中抓取当前帧 - HTMLCanvasELement:
<canvas>
- HTMLImageElement:
- 后面绘制的会覆盖前面的
canvas绘画状态的保存和恢复
ctx.save()和ctx.restore(),成对存在,栈的形式存储,先进后出- canvas会话状态
- 变形(移动、旋转、缩放)
- 属性:storkeStyle,fillStyle,globalAlpha(全局的透明度),lineWidth,lineCap,lineJoin,font,textAlign......
- 裁切路径(clipping path)
canvas坐标系形变
- canvas的形变是坐标系的变化
- 4种形变
ctx.translate(x,y)平移坐标系ctx.rotate(弧度)顺时针旋转坐标系ctx.scale(x,y)放大缩小- transform(a,b,c,d,e,f) 矩阵
- 注意:
- 形变前,调用
ctx.save()保存坐标系状态 - 形变后,调用
ctx.restore()恢复坐标系状态 - 先形变,再绘制图形
- 形变前,调用
canvas动画
-
对画布上的图形进行一帧一帧的重绘,1秒60帧可绘制流畅动画
-
定时重绘方法:setInterval、setTimeout、requestAnimationFrame,定期执行函数进行重绘
- setInterval定时器不精准,因为回调函数要放在宏任务中等待执行,如果一直有未处理的微任务,就不会在指定时间内触发回调,requestAnimationFrame更精准
- requestAnimationFrame,会在下次重绘之前调用该函数的回调函数来更新动画,通常每秒钟回调60次左右,要一直执行就要回调自己
-
画一帧动画的步骤
ctx.clearRect(x,y,width,height)清空canvasctx.save()- 绘制动画图形
ctx.restore()
秒针
<body>
<canvas id="secondCanvas" width="300" height="300"></canvas>
</body>
<script>
let canvasEl = document.getElementById("secondCanvas")
let ctx = canvasEl.getContext('2d')
console.log('%O', ctx) // 打印元素对象
requestAnimationFrame(() => {
draw() // 在重绘前调用,浏览器1秒重绘大约60次
})
function draw() {
let seconds = new Date().getSeconds()
console.log('seconds',seconds)
ctx.clearRect(0,0,300,300) // 清空绘画区域,重新绘制
ctx.save() // 保存状态
ctx.translate(100,100)
ctx.rotate(2*Math.PI/60 * seconds) // 旋转坐标轴
ctx.lineWidth = 4
ctx.strokeStyle = "lightBlue"
ctx.beginPath() // 开始绘制路径
ctx.moveTo(0,0) // 移动到开始位置
ctx.lineTo(0,-50)
ctx.stroke() // 描边
ctx.restore() // 恢复初始状态
requestAnimationFrame(draw) // 再次调用
}
</script>
SVG
- svg 是基于XML开放标准矢量图形格式
- 创建
- svg文件,xml
<svg></svg>标签,js创建标签- 其他软件
- svg优点:矢量图形放大缩小不会失真;可用js和css进行操作;可以动画;尺寸小;利于SEO;可压缩等
- svg缺点:不适合像素级操作;图像变得复杂时,加载会慢
- svg应用场景
- logo、icon和其他几何设计
- 适配多种尺寸的屏幕
- 简单动画
- 制作各种图标,大屏可视化页面开发
svg 和 canvas
- 可扩展性:svg放大缩小不会失真;canvas由像素点构成,放大会模糊
- 渲染能力:svg复杂时,dom也会变得复杂,渲染慢;canvas能高性能渲染和有更快的图形处理能力,适合制作H5小游戏
- 灵活度:svg -> js、css;canvas -> js、动画得一帧帧重绘
- 使用场景:svg -> 矢量徽标logo、图标icon和其他集合设计;canvas -> 游戏开发、绘制图形、复杂照片合成、对图片进行像素级操作
svg grid坐标系(网格系统)
- 和canvas差不多,左上角(0,0)为坐标原点,默认以像素为单位(单位可改:mm、cm、pt_点等
- )
- 元素有个初始视口坐标系,元素左上方
- transform,元素内部会建立新的坐标系统
视口viewport
- svg可见区域
- 两个坐标系
- 默认都为左上角(0,0)
- 视口坐标系
- 用户坐标系:viewbox属性修改
- 支持任意缩放:用户坐标系绘制的图形,参照视口坐标系进行等比例缩放
视图框viewBox
viewBox = <x> <y> <width> <height>x、y不修改坐标系,确定显示的开始坐标- viewBox 根据 viewport,等比放大,默认居中展示
perserveAspectRatio:none拉伸铺满;perserveAspectRatio:xMinYMin最小xy
<svg width="200" height="200"
viewBox="0 0 100 100"
style="border: 1px solid lightblue"
>
<circle cx="50" cy="50" r="50"></circle>
</svg>
绘制
- svg支持的基本形状有:矩形、圆形、椭圆、线条、折线、多边形、路径
- 文字、图形
- path,形状根据属性 d (命令 + 参数)定义
- 大写字母,绝对定位;小写字母,相对定位
- 直线命令:M/m:move to;L/l:line to;Z/z:close path等
- 曲线命令:C:三次贝塞尔曲线等
<svg width="500" height="500" style="border: 1px solid lightblue;">
<!-- rx、ry圆角 -->
<rect x="10" y="10" width="50" height="80" rx="5" ry="5" fill="orange"></rect>
<circle cx="140" cy="20" r="10" fill="yellow"></circle>
<ellipse cx="200" cy="60" rx="40" ry="20" fill="lightgreen"></ellipse>
<line x1="100" y1="100" x2="400" y2="100" stroke="red" stroke-with="5"></line>
<!-- 默认填充黑色不闭合 -->
<polyline points="10 200, 110 200, 60 300" fill="transparent" stroke="red"></polyline>
<!-- 默认填充黑色闭合 -->
<polygon points="210 200, 310 200, 260 300" fill="transparent" stroke="red"></polygon>
<!-- 默认填充黑色不闭合 -->
<path d="M 20 350, L 120 350, L 20 450" fill="transparent" stroke="blue"></path>
<!-- 默认填充黑色闭合 -->
<path d="M 220 350, L 320 350, L 220 450 Z" fill="transparent" stroke="blue"></path>
<!-- 图片会自动缩放 -->
<image href="./坐标系.png" x="400" y="400" width="100" height="100"></image>
<!-- text-anchor 文本流方向 start(默认)、middle、end、inherit -->
<!-- dominant-baseline 基线对齐方向 auto(默认)、middle、hanging -->
<text x="400" y="400" text-anchor="middle" dominant-baseline="middle">TEXT</text>
</svg>
组合和复用
- 元素的组合(容器)
<g></g>,属性会被子元素继承,可以通过<use>元素引用 - 复用元素
<defs></defs>,定义基本图形、组合图形、样式等,不会直接显示 - 复用元素
<symbols></symbols>,和defs类似,有viewBox、x、y、width、height属性 <symbols>和<defs><symbols>有更多的属性,<defs>无<symbols>有用户坐标系viewBox,可以制作svg精灵图
<use href="#" x="" y="" width="" height=""></use>复用,从svg中获取节点,赋值到指定地方
svg精灵图
<!-- 精灵图 -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" style="display:none">
<symbol id="chelizi" viewBox="0 0 1024 1024">
<path d="M330.3424 549.8368s89.7536 9.216 99.7376-300.6976l35.328-47.0528 25.6 35.84s73.5744 231.8848 218.6752 312.32c-15.7184 33.2288-51.2 43.5712-51.2 43.5712S509.2864 465.92 474.9824 321.4848c0 107.8784-40.96 249.0368-83.3536 267.3664s-61.2864-39.0144-61.2864-39.0144z" fill="#CF9782" />
</symbol>
<symbol id="caomei" viewBox="0 0 1024 1024">
<path d="M449.536 282.368s-240.64-25.6-280.9344 200.448 244.992 487.7824 442.4192 453.0688c83.3536-14.6432 156.1088-105.5232 205.8752-208.7424a807.5008 807.5008 0 0 0 36.864-90.2144c27.4944-81.0496 39.2192-158.72 29.5424-203.1104-25.9072-118.784-225.6896-212.1728-350.8224-155.2896-31.5904 1.3824-82.944 3.84-82.944 3.84z" fill="#FF8697" />
</symbol>
</svg>
<!-- 使用 -->
<svg width="50" height="50">
<!-- 根据画布放大缩小 -->
<use href="#chelizi"></use>
</svg>
<svg width="50" height="50">
<!-- 重新指定大小 -->
<use href="#caomei" width="30" height="30" fill="lightblue"></use>
</svg>
填充和描边
fill = "color|currentColor",currentColor(继承自身或者父亲字体color)stroke-dasharray = "3 5"填色区域为3,空5
<svg style="color: lightcoral;">
<!-- opacity 0-1 -->
<rect
x="10" y="10" width="50" height="50"
fill="currentColor"
fill-opacity="0.8"
>
</rect>
<!-- stroke-linecap 端点样式
srroke-linejoin 连接处样式
stroke-dashoffset dasharray模式下的偏移量 -->
<rect
x="80" y="10" width="50" height="50"
fill = "transparent"
stroke = "currentColor"
stroke-opacity = "0.8"
stroke-width = "3"
stroke-linecap = "butt"
srroke-linejoin = "miter"
stroke-dasharray = "10 5"
stroke-dashoffset = "4"
>
</rect>
</svg>
渐变
<svg>
<defs>
<!-- 渐变内容定义在 def 标签内部,指定一个id -->
<!-- x1="0" y1="0" x2="1" y2="0" 指定渐变方向 -->
<linearGradient id="gradient" x1="0" y1="0" x2="1" y2="0">
<!-- stop节点,在特定位置上(offset)应用什么颜色(stop-color) -->
<stop offset="0%" stop-color="red" stop-opacity="0.2"></stop>
<stop offset="50%" stop-color="green"></stop>
<stop offset="100%" stop-color="blue"></stop>
</linearGradient>
</defs>
<!-- fill 或 stroke 属性通过 id 引用 linearGradient 节点 -->
<rect x="0" y="0" width="100" height="100" fill="url(#gradient)"></rect>
</svg>
- 滤镜
<filter></filter>
形变transform
- 元素内部会建立新坐标系,后续绘图或形变会参照新坐标系
- translate(x,y)
- rotate(deg) rotate(deg,cx,cy) 绕z轴旋转,cx/cy指定旋转原点
- css transform-origin 移动坐标原点
- canvas translate 移动坐标原点
- scale(xn,yn)
- skew(x,y)
<svg>
<!-- transfrom,元素内部会参照新的坐标系,有先后顺序 -->
<g transfrom="translate(50, 50)">
<rect x="10" y="0" width="50" height="50" fill="green"></rect>
<!-- x,y,width,height都放大两倍 -->
<rect transform="scale(2) "x="10" y="20" width="50" height="50" fill="yellow" ></rect>
<!-- 参照父元素的坐标系,移动后根据左上角原点旋转 -->
<rect transform="translate(130,0) rotate(45, 0, 0)" x="0" y="0" width="50" height="50" fill="green"></rect>
</g>
</svg>
描边动画
- 绘制线
- 设置为虚线,偏移到不可见处
- 添加动画
<style>
.line {
stroke: aqua;
/* 156px填充颜色,156px空白*/
stroke-dasharray: 156px;
/* 移动156px到空白区域,故不可见 */
stroke-dashoffset: 156px;
/* forwards 一开始显示最后一帧 */
animation: lineMove 2s linear forwards;
animation-delay: 1s;
}
@keyframes lineMove {
100% {
/* 无偏移量,可见 */
stroke-dashoffset: 0px;
}
}
</style>
<body>
<svg>
<line x1="10" y1="10" x2="120" y2="120" class="line"></line>
</svg>
<script>
window.onload = function() {
getPathLength("line")
}
function getPathLength(className) {
let el = document.getElementsByClassName(className)[0]
let length = el.getTotalLength()
console.log(className, length) // 155.56...
}
</script>
</body>
svg动画实现方式
- js脚本 setInterval
- css样式 animation
- SMIL
- SMIL是可扩展标记语言(和HTML一样),描述多媒体演示
- SVG中使用SMIL元素(
<set>、<animate>、<animateMotion>等)实现动画
- SMIL实现动画的优势:声明式动画让浏览器自动处理,不需要手动管理动画时间,用animate元素就可以实现动画效果
SMIL - set元素
<svg>
<!-- set:设置某一个属性,没有过程 -->
<rect id="rectangle" x="10" y="10" width="100" height="80" fill="lightblue">
<!-- attributeName:css属性或者元素属性的名称 -->
<!-- begin="2s" 2s后开始 -->
<!-- begin="rectangle.click" 点击特定id元素后开始 -->
<set
attributeName="x"
to="200"
begin="2s"
>
</set>
</rect>
</svg>
SMIL - animate元素
<svg width="300" height="300">
<!-- animate:某个属性创建过渡动画效果 -->
<rect x="10" y="10" width="100" height="80">
<!-- animate 基本使用,三个属性是必须 -->
<animate
attributeName="x"
to="200"
dur="2s"
>
</animate>
</rect>
<!-- 设置不同的属性,两个动画会一起执行 -->
<rect id="rectangle" x="10" y="100" width="100" height="80" fill="lightblue">
<!-- fill 动画最终状态 freeze(最后一个动画帧)remove(第一个动画帧) -->
<animate
id="xAnimate"
attributeName="x"
from="100"
to="200"
dur="2s"
begin="2s"
fill="freeze"
repeatCount="1"
>
</animate>
<!-- value 动画过渡中的一系列值,分号隔开。values属性被定义时,form、to被忽略 -->
<!-- begin="xAnimate.end" xAnimate动画结束后执行 -->
<animate
attributeName="y"
values="100; 200"
begin="xAnimate.end"
dur="2s"
repeatCount="indefinite"
>
</animate>
</rect>
</svg>
SMIL - animateTransform元素
<svg width="300" height="300">
<circle cx="100" cy="100" r="50" fill="transparent" stroke-width="5" stroke="lightpink" stroke-dasharray="10,10">
<!-- animateTransform 存在多个时,后面会覆盖前面的 -->
<!-- type -> translate(x,y) | rotate(deg,cx,cy) | scale(x,y) | skewX(x) | skewY(y) -->
<animateTransform
attributeName="transform"
type="rotate"
values="0 100 100; 360 100 100"
dur="5s"
repeatCount="2"
>
</animateTransform>
</circle>
<!-- 进度条动画 -->
<circle cx="80" cy="100" r="5" fill="lightblue" stroke="none">
<animate
attributeName="opacity"
values="0;1;0"
dur="2s"
begin="0s"
repeatCount="indefinite"
>
</animate>
</circle>
<circle cx="100" cy="100" r="5" fill="lightblue" stroke="none">
<animate
attributeName="opacity"
values="0;1;0"
dur="2s"
begin="0.3s"
repeatCount="indefinite"
>
</animate>
</circle>
<circle cx="120" cy="100" r="5" fill="lightblue" stroke="none">
<animate
attributeName="opacity"
values="0;1;0"
dur="2s"
begin="0.6s"
repeatCount="indefinite"
>
</animate>
</circle>
</svg>
SMIL - animateMotion元素
<svg width="300" height="300">
<!-- animateMotion 一个元素如何沿着轨道移动 -->
<path id="pathLine" d="M 0 10, L 100 80, L 200 10" fill="transparent" stroke="lightblue"></path>
<!-- 调节x,y,使其居中在轨道上,一般为 0 0 -->
<rect id="rectangle" x="-8" y="-4" width="16" height="8" fill="pink"></rect>
<!-- path 运动轨迹(轨道),也可用 <mpath> -->
<!-- rotate 动画元素自动跟随轨道旋转 -->
<animateMotion
href="#rectangle"
dur="5s"
rotate="auto"
>
<!-- 复用路径作为轨道 -->
<mpath href="#pathLine"></mpath>
</animateMotion>
</svg>
其他
- Snap.svg库,专门处理svg的js库,类似于jq,比较便捷
- GSAP动画库,tewwn、delay、timeline(动画时间线),适用于HTML元素、svg、vue组件等的动画