可视化学习01

281 阅读12分钟

css3动画、canvas、svg

前端可视化技术

  • 底层图形引擎:Skia(c++,2/2.5d),OpenGL(c,更适合画3d)等
  • W3C提供:CSS3Canvas(基于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,旋转或倾斜会改变坐标系,不同顺序也会导致不同转换结果 image.png

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平面,三者之间的关系。 image.png

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>

image.png

两个cube旋转动画

浏览器渲染路程

image.png

  1. 解析HTML,构建 DOM tree
  2. 解析CSS文件,构建规则树
  3. DOM tree + 规则树 生成 render tree
  4. 布局layout:计算每个节点的宽度、高度、位置信息。元素大小、位置变化导致需要重新计算布局,成为回流
  5. 绘制paint:可见元素绘制在屏幕中
    • 标准流在同一层绘制
    • 一些特殊属性会创建新的层
    • 不影响布局的css修改导致重绘,回流必然重绘
  6. 合成层composite:一些特殊属性会创建新的合成层,并可以利用GPU来加速绘制,但是以消耗内存为代价,不能滥用

css3动画性能优化

  1. 创建新渲染层(减少回流)
    • 有明确的定位属性(display: relative/fixed/sticky/absolute)
    • 透明度(opacity 小于1)
    • transform
    • 对opacity、transform、filter 应用动画等等
  2. 创建合成层
    • 对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)
  • 点和路径

路径-绘制

  1. 创建路径起始点 ctx.beginPath()
  2. 使用绘图命令去画路径
    • 移动画笔 ctx.moveTo(x,y) 设置绘画起点
    • 绘制直线 ctx.lineTo(x,y) 设置直线终点
    • 绘制圆弧或圆 ctx.arc(x,y,radius,startAngle,endAngle,anticlockwise)
      • anticlockwise,逆时针方向,默认false(顺时针)
      • 弧度(radian):一个圆的弧度为 2*ΠR / R = 2Π rad
  3. 把路径闭合,不是必须 ctx.closePath()
  4. 路径生成后,通过描边或者填充渲染图形
    • ctx.stroke()
    • ctx.fill()
  • 上色,在描边或填充前
    • ctx.fillStyle = 'orange'
    • ctx.strokeStyle = 'blue'

线型 line style

  • lineWidth = value
  • lineCap = type
    • butt 截断,默认
    • round 圆形
    • squre 正方形
  • lineJoin = type
    • round 圆形
    • bevel 斜角
    • miter 默认

文本

  • 样式
    • ctx.font = value,10px sanc-serif(默认)
    • ctx.textAlign = value,start(默认)、end、left、right、center
    • ctx.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>
  • 后面绘制的会覆盖前面的

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)清空canvas
    • ctx.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>

image.png

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>

image.png

绘制

  • 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

image.png

<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) image.png
<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>

描边动画

  1. 绘制线
  2. 设置为虚线,偏移到不可见处
  3. 添加动画
<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组件等的动画