thanks kirupa


Transition的故事历经考验和磨难。从被无耻浏览器厂商所忽略而必须添加厂商前缀到目前被普遍接受,该旅程肯定是崎岖不平。尽管transition属性在过去几年有了很大进步,但与它更帅更靓的前辈animation相比,在实现某些东西方面并不容易。

我在CSS3: Animations vs. Transitions教程中完全描述transitions和animations的差异,在这里我将重申一个transition的重要局限。这个限制就是transitions没有一个属性指定让它循环。是的!在今天这个时代,transition被设计成只执行一次。的确是个笑话。

幸运的是,这里有一个方法。在本教程里,我将通过一个小伎俩解决 transitionend event 如何使transitions支持循环。

The Example

为了证实我有点疯狂,下面的例子展示了一个永远循环的transition。鼠标移到圆形上看到transition开始运行...永远不会停止。

注意到圆形从小到大的来回振荡。学完本章,你将能创建类似的动画并理解它的运行原理。

How to Make a Transition Loop

在深入做这件事之前,让我们首先退一步理清我们将要做的是什么。为了做到这点,先引入性感的圆圈吧:

Animation Img

接下来做的就是添加transition改变圆形的大小。圆形有一个初始大小。我们通称为它的初始状态stateOne。要实现transition须让它变的更大些,该状态为stateTwo

Animation Img

到目前为止看起来非常简单,对吗?我开始于stateOne,现在我触发一个transition改变圆的大小,为stateTwo状态定义的值:

Animation Img

由于我定义了transition,代替了它从stateOne突然转变到stateTwo状态,圆型会逐步变成大的尺寸。注意,如何触发transition使它的尺寸变到stateTwo状态是无关紧要的。有可能是元素的class值变化。有可能是鼠标移上指定一个不同的尺寸--就像刚刚这个例子。有可能是使用JavaScript代码直接修改它的大小。可以是各种方式写成这一点。怎样触发属性值的变化无关紧要。一旦定义了transition监听的属性发生了变化,transition将会执行。

现在,我提到所有关于transitions的东西你已经十分清楚,当作一个简单的回顾。让我们开始吧,因为这里要引入新东西。那就是让这个transition实现迭代。默认情况下,transition从stateOne过滤到stateTwo,其它什么都不会发生。有时,这取决于我的CSS设置,可能回到stateOne状态。我想要做的就是循环这个transition永不让它停止。一旦让transition运行,我想让圆型按我定义的大小stateOne和stateTwo来回振荡...永远。当它到达stateTwo状态,我想让它返回stateOne状态。当到达stateOne状态,再让它返回stateTwo状态...那么,你将得到一段影片。

下图就是我描述的影片的运行轨迹:

Animation Img

我们将如何做到这一点呢?当然不是最简单的!正如先前我提到的,问题在于CSS transition属性不支持内建的属性支持循环。我们要自己把事情搞清楚。这真是有点无耻。我们要是等待trasition完成做点点什么。一旦transition完成,我们将强制改变transition监听的属性值。通过改变transition监听的属性值,你的transition又会重新运行...直到新的属性变化到指定值。

现在我们有个好的方法如何让transition实现循环,让我们开始实现它吧。判断一个transition结束通过监听transitionend事件。在每次transitionend事件处理器中,我们将通过一些JavaScript代码修改transition监听着的属性值。在stateOne结束的transition中,transitionend事件将被触发。根据这一点,我们会通过JavaScript告知元素反向去stateTwo状态。当处于stateTwo状态,另一个transitionend事件将被触发。这时,告知元素反向去stateOne状态。你可以想象,这将继续一段时间。

这里有一个图展示了上面的循环:

Animation Img

最初transition可以被各种方式触发。它是怎样触发并不重要。重要的是后续的transition怎样被JavaScript触发的。

在接下来的几节中,让我们看看这个技巧吧。

Getting Started

好吧,我们最后动手实践说明前面的几个段落的内容并让它们运行。如果你不想动手实践而宁愿只是阅读和被的追随。跳过这个章节继续看下一个章节。如果你想积极跟随本教程并重新创建你在上面看到的例子,创建一个HTML文档把下面的HTML/CSS代码拷进去:

<!DOCTYPE html>
<html>

<head>
<meta content="en-us" http-equiv="Content-Language">
<meta charset="utf-8">
<meta content="stuff, to, help, search, engines, not" name="keywords">
<meta content="What this page is about." name="description">
<meta content="An Interesting Title Goes Here" name="title">
<title>An Interesting Title Goes Here</title>
<style>
body {
    background-color: #FFF;
    margin: 30px;
    margin-top: 10px;
}
#box {
    width: 550px;
    height: 350px;
    border: 5px black solid;
    overflow: hidden;
    background-color: #F2F2F2;
    margin: auto auto;
    background-image: url('http://www.kirupa.com/images/gray_background.png');
}
#contentContainer {
    position: relative;
}
#circleDiv {
    background-color: #2693FF;
    border-radius: 75px 75px 75px 75px;
    height: 150px;
    margin: 100px auto auto;
    width: 150px;
    transition: transform .2s ease-in-out, opacity .2s ease-in-out;
}
.stateOne {
    opacity: 1;
    transform: scale(1, 1);
}
.stateTwo {
    opacity: .5;
    transform: scale(1.9, 1.9);
}
p {
    font-family: Cambria, Cochin, Georgia, Times, "Times New Roman", serif;
    font-size: medium;
    color: #006699;
}

</style>
</head>

<body>
<div id="box">
    <div id="contentContainer">
        <div id="circleDiv" class="stateOne">

        </div>
    </div>
</div>

<script src="http://www.kirupa.com/html5/examples/js/prefixfree.min.js"></script>
<script>
// your code goes here
</script>
</body>
</html>

在浏览器中预览该文档,你将看到类似刚前的例子。唯一不同的就是这个示例的动画不能迭代。事实上,它是静态的什么都做不了,我们很快就会解决这个问题,但首先,让我们来看看到底如何进行处理:

在代码里,圆型被定义成:

<div id="circleDiv" class="stateOne">

</div>

有趣的细节是元素的idcircleDiv名为stateOne。一会儿你就会看到它们将出现在CSS和JavaScript代码里。首先看一下CSS...#circleDiv定义的样式:

#circleDiv {
    background-color: #2693FF;
    border-radius: 75px 75px 75px 75px;
    height: 150px;
    margin: 100px auto auto;
    width: 150px;
    transition:transform .2s ease-in-out, opacity .2s ease-in-out;
}

这个样式是让方形的div元素变成圆形。除些以外,也在这里定义了transition。注意,这里定义的transition包括transformopacity两个属性。

接下来看一下stateOnestateTwo样式规则,它是扮演在两个状态触发转换以及保持transition运行的角色:

.stateOne {
    opacity: 1;
    transform: scale(1, 1);
}
.stateTwo {
    opacity: .5;
    transform: scale(1.9, 1.9);
}

在上面两个规则中,opacitytransform两个属性均被设置成不同的值。如果从前面几行你还记得我们的transition属性设置为应对变化transformopacity。嗯…关键点正在此处!

Triggering The Transition Initially

第一件事是要我们的transition简单触发运行,这需要鼠标移上该元素。目前,正如果在HTML中看到的,元素有初始类stateOne的样式值。当我鼠标移上去后,想让它变为stateTwo样式的值。这些类名映射到选择器的样式规则,正如刚刚你看到的....stateOne.stateTwo

实现它的唯一方式就是写一些JavaScript。在script标签内添加如下行:

var theCircle = document.querySelector("#circleDiv");

function setup() {
    theCircle.addEventListener("mouseover", setInitialClass, false);
}
setup();

function setInitialClass(e) {
    theCircle.className = "stateTwo";
}

这段代码应该相当简单。通过调用querySelector变量theCircle指向#circleDiv元素。在这里我们设置一个mouseover事件监听器setInitailClass当鼠标移上去时触发。在setInitialClass事件处理器内部,将元素的类stateOne改变为stateTwo。

再预览一下HTML文档。当你鼠标移到元素上时,你将看到transition被触发。它的触发原理就是当鼠标移上去后,元素的类值由stateOne改变为stateTwo。意思是,定义的transition像鹰一样监视着元素的opacitytransform属性的变化。

当然,一旦transition完成后,什么都不会发生。至少这里你什么情况都没看到。因为transitionend事件没有生效,所以这里表面上什么都没有发生。在下一章节,让我们看看有什么面临的麻烦事!

Causing our Transition to Loop

现在,经过.2秒动画之后,元素的class属性值变为stateTwo并变大。我们要做的就是把元素类名变回stateOne,要做的就是监听transitionend事件。修改一下代码,看下面高亮行部分的代码:

var theCircle = document.querySelector("#circleDiv");

function setup() {
    theCircle.addEventListener("mouseover", setInitialClass, false);

    theCircle.addEventListener("transitionend", loopTransition, false);
    theCircle.addEventListener("webkitTransitionEnd", loopTransition, false);
    theCircle.addEventListener("mozTransitionEnd", loopTransition, false);
    theCircle.addEventListener("msTransitionEnd", loopTransition, false);
    theCircle.addEventListener("oTransitionEnd", loopTransition, false);
}
setup();

function setInitialClass(e) {
    theCircle.className = "stateTwo";
}

function loopTransition(e) {
    if (e.propertyName == "opacity") {
        if (theCircle.className == "stateTwo") {
            theCircle.className = "stateOne";
        } else {
            theCircle.className = "stateTwo";
        }
    }
}

刚添加的代码的大部分在transitionend事件教程有详细的描述。所以,我要跳过这部分比较容易的内容。相反,我想专注于loopTransition事件处理器发生了什么---当一个transitionend事件触发时该事件处理器被调用:

function loopTransition(e) {
    if (e.propertyName == "opacity") {
        if (theCircle.className == "stateTwo") {
            theCircle.className = "stateOne";
        } else {
            theCircle.className = "stateTwo";
        }
    }
}

上面高亮行的代码(if代码块)等价于下图展示意义:

Animation Img

loopTransition每次被调用,代码将检测元素上的class属性。如果元素当前的值为stateTwo,那么就将它的class值设置为stateOne,以再次触发transition:

Animation Img

如果当前元素的class为stateOne时loopTransition被调用,你应猜到设置class值为stateTwo,再次触发 transition:

Animation Img

上述高亮行的代码目的就是保证transition永远追逐一个移动的靶子。换句话说,那5行代码的职责就是使transition循环。

Conclusion

好吧,本篇内容要比我预期的更多一些。不管怎么说,你的目标就是保证transition永远不会停下来。方法就是确定监听transitionend 事件,当事件触发时修改元素被监听的属性。具体怎么实现你已经学会了,接下来的教程,我将带来一些结合所有知识点,比较酷的你从未见的例子。


翻译水平有限,敬请各位同学批评指正。


Comments

comments powered by Disqus