D3学习之动画和变换
(17.02.27-02.28)
主要学习到了D3对动画和缓动函数的一些应用,结合前面的选择器、监听事件、自定义插值器等,拓展了动画的效果和样式。
主要内容
- 单元素动画
- 多元素动画
- 使用缓动函数
- 使用中间帧函数
- 使用级联过渡
- 使用选择器过渡
- 监听过渡事件
- 自定义插值器
- 使用计时器
为什么需要动画?
人类视觉系统是一个精妙的信息处理器,因此推向可以传递海量信息,并且移动的图像更能在短时间内传达更多的信息。的确,在世界不断的演变过程中,人类的视觉系统也在不断地进化,对于移动的物体,它能够更好地聚焦。 -Parent R.2012
D3的动画与过渡
D3过渡使我们可以在网页上使用HTML或SVG创造计算机动画。D3过渡实现了一种基于插值的动画(Interpolation-based Animation)。所以D3动画的基础是插值。
单元素动画
body.append("div")
.classed("box", true)
.style("background-color", "#e9967a")
.transition() //使用d3.selection.transition函数来定义一个过渡
.duration(duration) //使用duration函数来设置过渡效果的持续时间
.style("background-color", "#add8e6")
.style("margin-left", "600px")
.style("width", "100px")
.style("height", "100px")
.transition() //要实现单元素连续动画就直接加在后面
.duration(duration)
.style("background-color", "#19e549")
.style("margin-top", "100px")
.style("margin-left","100px")
.style("width", "500px")
.style("height", "50px");
因为是动画所以不好截图显示,从代码上也可以看出是一个长方形变成正方形再变成长方形,同时伴随着颜色的变化的过程。
多元素动画
原理和单元素类似,但是操作对象变成了一个集合而不是单一的元素:
selection //selection是元素的集合
.transition().duration(duration) // <-D
.style("top", function (d) {
return chartHeight - barHeight(d) + "px"; //top是距离顶的距离
})
.style("left", function (d, i) { //更新left值和height值
return barLeft(i) + "px";
})
.style("height", function (d) {
return barHeight(d) + "px";
})
.style("background-color",randomColor())
.select("span")
.text(function (d) {
return d.value;
}); //d.value,d是字典
缓动函数
过渡是和时间相关的函数,它将时间进度映射到数值的变化,形成了对象的运动(如果数值代表位置)或者形变(如果数值描述视觉属性)。时间是均匀变化的,换句话说时间是均匀的,然而结果并不总是需要均匀的。缓动正是控制这一映射,并提供灵活性的典型技术。当一个过渡生成均匀的值变化时,我们称之为线性缓动。
D3内部已经实现了一部分缓动函数及其过渡效果(linear线性,cubic立方,sin正弦等),同时支持自定义函数(代码内的B处)。
var data = [ // <-A
"linear", "cubic", "cubic-in-out",
"sin", "sin-out", "exp", "circle", "back",
"bounce",
function(t){ // <-B
return t * t;
}
]
ease( )的传入参数是一个字符串,D3会找到同名的缓动函数,否则默认使用线性缓动。而在实现上,需要注意的有一点:
d3.selectAll("div").each(function(d){
d3.select(this)
.transition().ease(d) //这里ease()函数不能用上面each()这种方式
.duration(5000)
.style("left", "10px");
});
ease()函数不支持以下这种方式,即使用一个函数来定义不同的缓动效果:
d3.selectAll("div").ease(function(d){
return d;})
.duration(5000)
.style("left", "10px");
});
D3还提供了缓动模式修饰符,它能够和任意缓动函数结合起来,形成特殊的效果,例如sin-out或者quad-out-in。现有的模式修饰符有如下几个:
- in:默认
- out:反向
- in-out:镜像
- out-in:反向镜像
使用中间帧计算
中间帧一词源于“inbetween”,inbetween是传统动画行业的一种通用实践,当时主设计师创建完关键帧后,再由工作人员在其中插入一些中间帧。这一概念被引入现代计算机动画中,用来代表插入中间帧的各种技术和算法。
以下函数创建了一个自定义中间帧计算函数:
body.append("div").append("input")
.attr("type", "button")
.attr("class", "countdown")
.attr("value", "0")
.transition().duration(duration).ease("linear")
.styleTween("width", widthTween)
.attrTween("value", valueTween); //valueTween即中间帧计算函数
来看看valueTween():通过量化尺度对传入的时间参数插值,最终生成了跳跃的整数效果。
function valueTween(){
var interpolate = d3.scale.quantize() // 定义了量化尺度,设置了定义域和值域
.domain([0, 1])
.range([1, 2, 3, 4, 5, 6, 7, 8, 9]);
return function(t){ // <-D
return interpolate(t);
};
}
效果如下:
级联过滤
级联过滤的作用就是将复杂的过渡效果进行封装,从而可以重复使用,保证了复杂过渡效果的可重用性,这一特性很好地实现了DRY原则(Don't repeat yourself)。
function teleport(s) { //复杂的过渡效果
s.transition().duration(300)
.style("width", "200px")
.style("height", "1px")
.transition().duration(100)
.style("left", "600px")
.transition().duration(300)
.style("left", "800px")
.style("height", "80px")
.style("width", "80px")
.transition().duration(300)
.style("left", "680px")
.style("height", "1px")
.style("width", "200px")
.style("top", "80px")
.transition().duration(100)
.style("left", "10px")
.transition().duration(300)
.style("height", "80px")
.style("width", "80px")
.style("top", "10px");
}
调用这一函数:
body.append("div")
.attr("class", "dong")
.style("position", "fixed")
.style("background-color", "steelblue")
.style("left", "10px")
.style("width", "80px")
.style("height", "80px")
.call(teleport); //通过call函数来调用级联过滤
使用选择性过渡
选择性过渡用于对特定选集的部分子集应用过渡效果。以下选择器限定了data值为“cat”的对象才会移动。
.transition() // <- A
.duration(duration)
.style("left", "10px")
.filter(function(d){return d == "Cat";}) // <- B d就是数据值 选择器
.transition() // <- C
.duration(duration)
.style("left", "500px");
效果如图:
级联过滤
级联过滤的作用就是将复杂的过渡效果进行封装,从而可以重复使用,保证了复杂过渡效果的可重用性,这一特性很好地实现了DRY原则(Don't repeat yourself)。
function teleport(s) { //复杂的过渡效果
s.transition().duration(300)
.style("width", "200px")
.style("height", "1px")
.transition().duration(100)
.style("left", "600px")
.transition().duration(300)
.style("left", "800px")
.style("height", "80px")
.style("width", "80px")
.transition().duration(300)
.style("left", "680px")
.style("height", "1px")
.style("width", "200px")
.style("top", "80px")
.transition().duration(100)
.style("left", "10px")
.transition().duration(300)
.style("height", "80px")
.style("width", "80px")
.style("top", "10px");
}
调用这一函数:
body.append("div")
.attr("class", "dong")
.style("position", "fixed")
.style("background-color", "steelblue")
.style("left", "10px")
.style("width", "80px")
.style("height", "80px")
.call(teleport); //通过call函数来调用级联过滤
使用选择性过渡
选择性过渡用于对特定选集的部分子集应用过渡效果。以下选择器限定了data值为“cat”的对象才会右移。
.transition() // <- A
.duration(duration)
.style("left", "10px")
.filter(function(d){return d == "Cat";}) // <- B d就是数据值 选择器
.transition() // <- C
.duration(duration)
.style("left", "500px");
效果如图:
监听过渡事件
监听用于在触发特定动作后进行相应的操作或者在过渡时进行不同的处理。
.transition().duration(duration)
.delay(1000)
.each("start", function(){ // 在过渡开始(start)时触发,修改text值
console.log(arguments);
d3.select(this).text(function (d, i) {
return "transitioning";
});
})
.each("end", function(){ // 在过渡动画结束时修改text值为done
d3.select(this).text(function (d, i) {
return "done";
});
})
效果如下:
实现自定义插值器
先看看自定义插值器的实现:
d3.interpolators.push(function(a, b) { // <-A
var re = /^([a-z])$/, ma, mb;
if ((ma = re.exec(a)) && (mb = re.exec(b))) {
a = a.charCodeAt(0);
var delta = a - b.charCodeAt(0);
return function(t) {
return String.fromCharCode(Math.ceil(a - delta * t));
};
}
}); //自定义差值器自动加入全局,并且优先被选取(类似栈结构)
这里定义的是一个a-z的插值器,设定了检查和范围,那么在调用时只要符合参数的范围(a-z),D3会调用相应的插值器。
countdown.attr("type", "button")
.attr("class", "countdown")
.attr("value", "a") //a
.transition().ease("linear")
.duration(25000).delay(1000)
.attr("value", "z"); //z
效果如图:
使用定时器
D3定时器函数作为实现D3过渡的底层结构,可以帮助我们更灵活地创建自定义动画。
在下面这个例子中,我们构造一个自定义动画,用来显示不断变化的从0到100的数字。
function countup(target){
d3.timer(function(){
var value = countdown.attr("value");
if(value == target) return true;
countdown.attr("value", ++value);
});
}
d3.timer()函数接受一个自定义函数,并且立即反复调用这一函数,直到该函数返回true为止,因此当value等于target(也就是100)时,才会返回true,否则就会一直自加。
最后
已掌握了对过渡和动画的初步使用,在以后具体实现中应多思考来实现更复杂的效果。