• d3 插值-实现一个简单的 animation


    本篇介绍插值的一种典型应用 动画: (搜索关键词: 补间动画)

    要实现一个动画, 一般会在动画过程中定义关键帧, 然后在帧之间创建"补间": 计算出在每一个时间点下物体的形状、位置、颜色等信息, 然后绘制物体即可;

    一个简单的支持插入关键帧的动画方法, 以下是完整示例代码; 

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <style>
            .box{
                width: 200px;
                height: 200px;
                border: 5px solid #333;
                background-color: green;
                font-size: 30px;
                color: #fff;
                font-weight: bold;
            }
        </style>
    </head>
    <body>
    
        <div class="box">这是一个盒子</div>
    
        <script>
    
            window.onload = function() {
                var elem = document.querySelector('.box');
                var animate = new Animate({left: 0, top: 0, text: 1}, {left: 1200, top: 300, text: 500,  400}, 5000, easeInOut).addTween(
                    {
                        k: 0.4,
                        value: {
                            left: 300,
                            top: 500,
                            text: 300,
                             140
                        }
                    },
                    {
                        k: 0.6,
                        value: {
                            left: 1000,
                            top: 300,
                            text: 400,
                             300
                        }
                    },
                    {
                        k: 0.8,
                        value: {
                            left: 800,
                            top: 500,
                            text: 200,
                             200
                        }
                    }
                );
                
                var time1 = Date.now()
                animate.begin()
                animate.on('update', function(attrs) {
                    elem.innerHTML = parseInt(attrs.text);
                    elem.style.width = (attrs.width || 100) + 'px'
                    elem.style.transform = `translate(${attrs.left}px, ${attrs.top}px)`;
                })
                animate.on('finish', function(attrs) {
                    var time2 = Date.now()
                    console.log('动画结束!', attrs)
                    console.log('累计耗时::', time2 - time1);
                })
                elem.addEventListener('click', function(e) {
                    if (e.target.dataset.lock == '1') {
                        animate.continue()
                        e.target.dataset.lock = '0'
                    } else {
                        animate.pause()
                        e.target.dataset.lock = '1'
                    }
                })
            }
    
            function linear(k) {
                return k
            }
            function easeIn(k) {
                return k * k;
            }
            function easeInOut(k) {
                if ((k *= 2) < 1) {
                    return 0.5 * k * k;
                }
                return - 0.5 * (--k * (k - 2) - 1);
            }
            
            function type(value) {
                return Object.prototype.toString.call(value).match(/[A-Z][a-z]+/)[0].toLowerCase()
            }
    
    
            function Animate(start, end, duration, delay, easeing){
                this.start = start;
                this.now = start
                this.end = end;
                this.duration = duration || 1000;
                this.easeing = easeing || linear;
                this.initTimestamp = Date.now();
                this.stackTimestamp = 0;
                this.requestAnimation = null
    
                this.eventHandlers = {}
                // this.delay = delay || 0
    
                this.dis = [Animate.sub(start, end)];
                this.frames = [{value: this.start, k: 0}, {value: this.end, k: 1}]
            }
            // 
            Animate.prototype.resetFrameDis = function() {
                var i = 0, len = this.frames.length, res = [], next, cur
                for(;i < len-1;i++ ) {
                    next = this.frames[i+1]
                    cur = this.frames[i]
                    res.push(Animate.sub(cur.value, next.value))
                }
                this.dis = res
            }
            //
            Animate.prototype.emit = function(key) {
                var list = this.eventHandlers[key];
                var _this = this;
                if (list && list.length) {
                    list.forEach(function(fn) {
                        typeof fn === 'function' && fn(_this.now)
                    })
                }
            }
            //
            Animate.prototype.on = function(key, callback) {
                this.eventHandlers[key] = this.eventHandlers[key] || [];
                this.eventHandlers[key].push(callback)
            }
            //
            Animate.prototype.off = function(key, callback) {
                if (callback === undefined && typeof key === 'string') {
                    delete this.eventHandlers[key]
                } else if (callback === undefined && key === undefined) {
                    this.eventHandlers = {}
                } else if (typeof callback === 'function' && typeof key === 'string') {
                    var index = this.eventHandlers[key].indexOf(callback);
                    if (index === -1) {
                        console.error('Error: function ', callback.name + ' is not in quene')
                    } else {
                        this.eventHandlers[key].splice(index, 1)
                    }
                }
            }
    
            //
            Animate.prototype.pause = function() {
                this.stackTimestamp += (Date.now() - this.initTimestamp)
                window.cancelAnimationFrame(this.requestAnimation)
            }
            //
            Animate.prototype.continue = function() {
                this.reset(Date.now())
                this.begin();
            }
            //
            Animate.prototype.reset = function(timestamp) {
                this.initTimestamp = timestamp;
            }
    
            Animate.prototype.update = function() {
                var curTimestamp = Date.now();
                var now
                var k = Animate.climp((curTimestamp - this.initTimestamp + this.stackTimestamp) / this.duration, 0, 1)
    
                var currentFrameIndex, currentFrameEaseing, dis;
                if (this.frames.length === 2) {
                    currentFrameEaseing = this.easeing(k)
                    
                    dis = Animate.multi(this.dis[0], currentFrameEaseing)
                    this.now = Animate.add(this.frames[0].value, dis);
                } else {
                    currentFrameIndex = this.getFrameIndex(k)
                    currentFrameEaseing = this.easeing(this.getFrameEasing(k));
    
                    dis = Animate.multi(this.dis[currentFrameIndex], currentFrameEaseing)
                    this.now = Animate.add(this.frames[currentFrameIndex].value, dis);
                }
            }
            
            Animate.prototype.begin = function() {
                var _this = this;
                var tick = function() {
                    if (_this.isFinish()) {
                        _this.emit('finish')
                        window.cancelAnimationFrame(_this.requestAnimation)
                        return
                    } else {
                        _this.update()
                        _this.emit('update')
                        _this.requestAnimation = window.requestAnimationFrame(tick)
                    }
                }
                tick()
            }
    
            Animate.prototype.isFinish = function() {
                return Animate.compareRightIsBigger(this.end, this.now)
            }
    
            //
            Animate.prototype.getFrameIndex = function(k) {
                var next = 0,
                    i = 0,
                    len = this.frames.length,
                    ck;
    
                for (; i<len; i++) {
                    ck = this.frames[i].k;
                    if (k > ck) {
                        next = i
                    }
                }
                
                return next
            }
            // 
            Animate.prototype.getFrameEasing = function(k) {
                var i = 0,
                    len = this.frames.length,
                    cur, next, pre;
                
                for (; i<len; i++) {
                    cur = this.frames[i];
                    if (k > cur.k) {
                        pre = cur.k
                        next = this.frames[i+1] ? this.frames[i+1].k : 1
                    }
                }
    
                return (k - pre) / (next - pre)
            }
            
            // 添加关键帧
            Animate.prototype.addTween = function() {
                var args = arguments,
                    len = args.length,
                    frame = [],
                    _k;
                for(var i = 0; i< args.length; i++) {
                    _k = args[i].k
                    if (args[i].hasOwnProperty('k') && _k > 0 && _k < 1) {
                        frame.push(args[i])
                    } else {
                        _k = (1 / (len + 1)) * i;
                        frame.push({k: _k, value: args[i]})
                    }
                }
    
                this.frames = this.frames.concat(frame)
                this.frames.sort(function(f1, f2) { return f1.k < f2.k ? -1 : 1 });
                this.resetFrameDis()
    
                return this
            }
    
            // 比较值大小
            Animate.compareRightIsBigger = function(min, max) {
                var tv1 = type(min);
                var tv2 = type(max);
    
                if (tv1 !== tv2) {
                
                    return Number(min) <= Number(max) ? true : undefined
                } else {
                    if (tv1 === 'number') {
                        return min <= max
                    } else if (tv1 === 'date') {
                        return min <= max
                    } else if (tv1 === 'string') {
                        return Number(min) <= Number(max)
                    } else if (tv1 === 'array') {
                        return min.every(function(v, i) { return min[i] == undefined || max[i] == undefined ? trye : min[i] <= max[i] })
                    } else if (tv1 === 'object') {
                        var isBigger = true
                        for (var key in min) {
                            if (!max.hasOwnProperty(key) || max[key] == undefined || min[key] == undefined) {
                                continue
                            }
                            isBigger = min[key] <= max[key]
                            if (isBigger === false) {
                                break
                            }
                        }
                        return isBigger
                    }
                }
            }
    
    
            // 相加
            Animate.add = function(value1, value2) {
                var tv1 = type(value1);
                var tv2 = type(value2);
                var output;
    
                if (tv1 !== tv2) {
                    output = Number(value1) + Number(value2)
                    return Animate.isNaN(output) ? undefined : output
                }
    
                if (tv1 === 'number') {
                    return value1 + value2
                } else if (tv1 === 'string') {
                    return Number(value1) + Number(value2)
                } else if (tv1 === 'date') {
                    
                    return value1 + value2
                } else if (tv1 === 'array') {
                    output = []
                    value1.forEach(function(v, i) { 
                        if (value1[i] != undefined || value2[i] != undefined) {
                            output[i] = Number(value1[i]) + Number(value2[i])
                        }
                    })
                    return output
                } else if (tv1 === 'object') {
                    output = {}
                    for (var key in value1) {
                        if (!value2.hasOwnProperty(key) || value2[key] == undefined || value1[key] == undefined) {
                            continue
                        }
                        output[key] = Number(value1[key]) + Number(value2[key])
                    }
                    return output
                }
            }
            // 相乘
            Animate.multi = function(value, k) {
                var tv = type(value), output;
                if (tv === 'string' && !Animate.isNaN(Number(value)) ) {
                    return +value * k
                } else if (tv === 'number') {
                    return value * k
                } else if (tv === 'date') {
                    return value * k
                } else if (tv === 'array') {
                    output = []
                    value.forEach(function(v, i) { 
                        if (v != undefined) { output[i] = +v * k  }
                    })
                    return output
                } else if (tv === 'object') {
                    output = {}
                    for (var key in value) {
                        if (value != undefined) {
                            output[key] = +value[key] * k
                        }
                    }
                    return output
                } else {
                    return output
                }
            }
            // 相减
            Animate.sub = function(start, end) {
                var ts = type(start);
                var te = type(end);
                var i = 0;
                var lenS, lenE, minLen, key, res
                if (ts !== te) {
                    res = Number(end) - Number(start)
                    return Animate.isNaN(res) ? undefined : res
                }
                
                if (ts === 'number') {
                    res = end - start
                } else if (ts === 'date') {
                    res = end - start
                } else if (ts === 'string') {
                    res = +end - start
                    res = Animate.isNaN(res) ? undefined : res
                } else if (ts === 'array') {
                    lenS = start.length;
                    lenE = end.length;
                    minLen = Math.min(lenE, lenS);
    
                    res = []
                    for (; i < minLen; i++) {
                        if (end[i] == undefined || start[i] == undefined) {
                            res[i] = null
                        } else {
                            res[i] = +end[i] - start[i]
                        }
                    }
                } else if (ts === 'object') {
    
                    res = {}
                    for(key in start) {
                        if (!end.hasOwnProperty(key) || end[key] == undefined || start[key] == undefined) {
                            continue
                        }
                        res[key] = +end[key] - start[key]
                    }
                }
    
                return res
            }
    
            // 
            Animate.type = function(v) {
                return Object.prototype.toString.call(value).match(/[A-Z][a-z]+/)[0].toLowerCase()
            }
            Animate.isNaN = function(v) { return v !== v }
            Animate.getPrecision = function(v) {
                return v - parseInt(v) === 0 ? 1 : v - parseInt(v)
            }
            // 
            Animate.climp = function(v, min, max) {
                if (v <= min) {
                    return min
                } else if (v >= max) {
                    return max
                } else {
                    return v
                }
            }
        </script>
    </body>
    </html>
  • 相关阅读:
    【翻译】Longest Palindromic Substring 最长回文子串
    java三大框架学习总结(1)
    select XXX into 和 Insert into XXX select
    在WinForm编程中犯的一些错误
    自定义类型数组排序的两种实现方式
    C# 释放非托管资源
    在已创建的DataTable对象中添加在首列一列
    WinForm编程时窗体设计器中ComboBox控件大小的设置
    php处理序列化jQuery serializeArray数据
    谈谈对程序员的培养
  • 原文地址:https://www.cnblogs.com/liuyingde/p/13566586.html
Copyright © 2020-2023  润新知