d3.js 入門學習記錄(六) 過渡動畫

WanFengZ發表於2019-11-17

d3 的過渡是設計是非常好的,基於核心插值器的過渡動畫讓我們可以通過短短几行程式碼就可以滿足專案所需的動畫效果,讓資料的展示效果更加優秀。

簡單過渡

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        .box {
            width: 200px;
            height: 200px;
            margin: 40px;
            float: left;
            text-align: center;
            border: #969696 solid thin;
            padding: 5px;
        }

    </style>
</head>
<body>
<script src="../d3.js"></script>
<script>
    d3.select('body')
        .append('div')
            .classed('box', true)
            .style('background-color', '#e9967a')
        .transition()
        .duration(5000)
            .style('background-color', '#add8e6')
            .style('margin-left', '600px')
            .style('width', '100px')
            .style('height', '100px')
</script>
</body>
</html>
複製程式碼

效果如下:

d3.js 入門學習記錄(六) 過渡動畫

selection.transition() 定義了一個過渡,然後用 duration() 設定過渡的時長為 5000ms, 然後我們設定了過渡結束後的屬性值,d3 會幫我們計算出對應的開始時的屬性值,並在過渡期間通過插值器和演算法自動填充對應的屬性。另外 transition() 是可以多次指定的,下一個 transition 會在上一個結束後開始。

資料繫結 + 過渡

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        * {
            padding: 0;
            margin: 0;
        }
        .baseline {
            height: 1px;
            background-color: black;
        }
        .v-bar {
            min-width: 30px;
            background-color: #4682b4;
            margin-right: 2px;
            font-size: 10px;
            color: #f0f8ff;
            text-align: center;
            display: inline-block;
        }
    </style>
</head>
<body>
<script src="../d3.js"></script>
<script>
    const data = [],
          duration = 500,
          chartHeight = 100,
          chartWidth = 640

    const push = (function () {
        let id = 0
        return function (data) {
            data.push({
              id: ++id,
              value: Math.round(Math.random() * chartHeight)
            })
        }
    })()

    for (let i = 0; i < 20; i++) {
        push(data)
    }
    
    function render(data) {
        const selection = d3.select('body').selectAll('div.v-bar')
            .data(data, function (d) {
                return d.id
            })

        selection.enter()
            .append('div')
                .classed('v-bar', true)
                .style('height', '0px')
                .style('position', 'fixed')
                .style('left', function (d, i) {
                    return i * 32 + 'px'
                })
                .style('top', chartHeight + 'px')
            .append('span')


        selection
            .transition().duration(duration)
                .style('height', function (d) {
                    return d.value + 'px'
                })
                .style('z-index', 0)
                .style('left', function (d, i) {
                    return i * 32 + 'px'
                })
                .style('top', function (d) {
                    return chartHeight - d.value + 'px'
                })
                .select('span')
                    .text(function (d) {
                        return d.value
                    })

        selection.exit()
            .transition().duration(duration)
                .style('left', function () {
                    return '-32px'
                })
            .remove()
    }

    setInterval(() => {
      data.shift()
      push(data)
      render(data)
    }, 2000)

    render(data)

    d3.select('body')
        .append('div')
            .classed('baseline', true)
            .style('width', chartWidth + 'px')
            .style('position', 'fixed')
            .style('z-index', 1)
            .style('top', chartHeight + 'px')
</script>
</body>
</html>

複製程式碼

效果如下:

d3.js 入門學習記錄(六) 過渡動畫

這個例子沒什麼好解釋的,看下就能理解,稍微注意下可以發現我們對於 exit 選擇集 也新增了個過渡效果,讓它移到顯示區域外再移除,這讓效果看起來更流暢。

使用ease函式

我們可以使用 ease() 來指定過渡的緩動曲線。delay() 設定過渡開始的延時。

關於d3內建的緩動型別可檢視 observablehq.com/@d3/easing-…

我們也可以自定義緩動曲線, 如 ease(t => t * t)

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        .fixed-cell {
            min-width: 40px;
            min-height: 20px;
            margin: 5px;
            position: fixed;
            text-align: center;
            border: #969696 solid thin;
            padding: 5px;
        }
    </style>
</head>
<body>
<script src="../d3.js"></script>
<script>
    const data = [
        {name: 'Linear', fn: d3.easeLinear},
        {name: 'Cubic', fn: d3.easeCubic},
        {name: 'CubicIn', fn: d3.easeCubicIn},
        {name: 'Sin', fn: d3.easeSin},
        {name: 'SinIn', fn: d3.easeSinIn},
        {name: 'Exp', fn: d3.easeExp},
        {name: 'Circle', fn: d3.easeCircle},
        {name: 'Back', fn: d3.easeBack},
        {name: 'Bounce', fn: d3.easeBounce},
        {name: 'Elastic', fn: d3.easeElastic},
        {name: 'Custom', fn: function (t) {
          return t * t
        }}
    ]

    const colors = d3.scaleOrdinal(d3.schemeCategory10)

    d3.select('body').selectAll('div')
        .data(data)
        .enter()
            .append('div')
                .classed('fixed-cell', true)
                .style('top', function (d, i) {
                    return i * 40 + 'px'
                })
                .style('left', '10px')
                .style('background-color', function (d, i) {
                    return colors(i)
                })
                .style('color', 'white')
                .text(function (d) {
                    return d.name
                })

    d3.selectAll('div').each(function (d) {
        d3.select(this)
            .transition().ease(d.fn)
            .duration(1500)
            .delay(3000)
                .style('left', '500px')
    })
</script>
</body>
</html>
複製程式碼

效果如下:

d3.js 入門學習記錄(六) 過渡動畫

使用中間幀

我們可以使用中間幀來指定過渡中的插值情況。

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<script src="../d3.js"></script>
<script>
    d3.select('body')
        .append('div')
        .append('input')
            .classed('countdown', true)
            .attr('type', 'button')
            .attr('value', 0)
        .transition().duration(5000).ease(d3.easeLinear)
            .styleTween('width', widthTween)
            .attrTween('value', valueTween)

    function widthTween() {
        const interpolate = d3.scaleQuantile()
          .domain([0, 1])
          .range([150, 200, 250, 350, 400])

        return function (t) {
          return interpolate(t) + 'px'
        }
    }

    function valueTween() {
      const interpolate = d3.scaleLinear()
        .domain([0, 1])
        .range([0, 1])

      return function (t) {
        return interpolate(t)
      }
    }

</script>
</body>
</html>

複製程式碼

效果如下:

d3.js 入門學習記錄(六) 過渡動畫

指定中間幀時函式必須為返回值為 引數為 t 的函式 的函式。 t 的定義域為[0, 1],代表著過渡的開始和結束。

我們通過 attrTween() , 將 width 的過渡設為離散值的過渡,input 寬度的變化並不是連續的。styleTween() 將 value 的 過渡設為線性過渡。

監聽過渡事件

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        .box {
            width: 200px;
            height: 200px;
            margin: 40px;
            float: left;
            text-align: center;
            border: #969696 solid thin;
            padding: 5px;
        }
    </style>
</head>
<body>
<script src="../d3.js"></script>
<script>
    d3.select('body')
        .append('div').classed('box', true)
            .style('background-color', 'steelblue')
            .style('color', 'white')
            .text('waiting')
            .transition().duration(3000).delay(3000)
                .on('start', function () {
                    d3.select(this).text('transitioning')
                })
                .on('end', function () {
                    d3.select(this).text('end')
                })
                .style('margin-left', '600px')
</script>
</body>
</html>
複製程式碼

效果如下:

d3.js 入門學習記錄(六) 過渡動畫

另外 d3 還新新增了 end() 方法,他會在過渡結束後返回一個promise物件方便我們呼叫。

相關文章