使用 CSS3 过渡实现美观的应用





5.00/5 (4投票s)
本文旨在首先介绍过渡的概念,然后介绍 CSS3 过渡的工作原理以及如何处理不支持该功能的老式浏览器。
一个美观的应用程序必须为用户提供视觉反馈。用户必须始终知道应用程序已接收并理解了某个指令(点击、触摸或其他任何操作),而动画是实现这一目标的绝佳工具。
新的HTML 5 规范(说实话,我应该说“新的CSS 3 规范”)引入了一个处理简单动画的绝佳工具:过渡。
根据 W3C 网站上“CSS Transitions Module Level 3”规范,CSS3 过渡允许 CSS 值属性的变化在指定的持续时间内平滑发生。
本文旨在首先介绍过渡的概念,然后介绍 CSS3 过渡的工作原理以及如何处理不支持该功能的老式浏览器。
此外,我建议您阅读 David Rousset 的《CSS3 动画入门》,这是一篇与本文相辅相成的优秀文章。
为了展示 CSS3 过渡的用法,我开发了一个游戏示例,该游戏使用 CSS3 过渡来为拼图的单元格添加动画(并且如果您的浏览器不支持 CSS3 过渡,它将回退到 JavaScript)。
CSS3 过渡
引言
起初,W3C CSS 工作组抵制将过渡添加到 CSS,认为过渡并非真正的样式属性。但最终,设计师和开发人员成功地说服了他们,过渡与动态样式有关,并且可以在 CSS 文件中使用。
根据 W3C 网站,CSS3 过渡能够为以下类型的属性添加动画:(点击此处显示它们)
- 颜色:通过红色、绿色、蓝色和 Alpha 分量进行插值(将每个视为数字,见下文)。
- 长度:作为实数进行插值。
- 百分比:作为实数进行插值。
- 整数:通过离散步长(整数)进行插值。插值在实数空间中进行,并使用 floor() 转换为整数。
- 数字:作为实数(浮点数)进行插值。
- 变换列表:请参阅 CSS Transforms 规范:http://www.w3.org/TR/css3-2d-transforms/
- 矩形:通过 x、y、宽度和高度分量进行插值(将每个视为数字)。
- 可见性:通过离散步长进行插值。插值在 0 到 1 之间的实数空间中进行,其中 1 表示“可见”,所有其他值表示“隐藏”。
- 阴影:通过颜色、x、y 和模糊分量进行插值(在适当的情况下将其视为颜色和数字)。如果存在阴影列表,则较短的列表将在末尾填充具有透明颜色的阴影,并且所有长度(x、y、模糊)均为 0。
- 渐变:通过每个停止点的位置和颜色进行插值。为了能够对它们进行动画处理,它们必须具有相同的类型(径向或线性)和相同数量的停止点。
- 绘制服务器(SVG):仅支持在渐变到渐变和颜色到颜色之间进行插值。然后它们会像上面一样工作。
- 以上项的空格分隔列表:如果列表项数量相同,则列表中的每个项都使用上述规则进行插值。否则,不进行插值。
- 简写属性:如果简写属性的所有部分都可以进行动画处理,则插值将执行,就好像每个属性都单独指定一样。
并且必须支持以下属性以进行过渡:(点击此处显示它们)
- background-color(颜色)
- background-image(仅限渐变)
- background-position(百分比和长度)
- border-bottom-color(颜色)
- border-bottom-width(长度)
- border-color(颜色)
- border-left-color(颜色)
- border-left-width(长度)
- border-right-color(颜色)
- border-right-width(长度)
- border-spacing(长度)
- border-top-color(颜色)
- border-top-width(长度)
- border-width(长度)
- bottom(长度和百分比)
- color(颜色)
- crop(矩形)
- font-size(长度和百分比)
- font-weight(数字)
- grid-*(各种)
- height(长度和百分比)
- left(长度和百分比)
- letter-spacing(长度)
- line-height(数字、长度和百分比)
- margin-bottom(长度)
- margin-left(长度)
- margin-right(长度)
- margin-top(长度)
- max-height(长度和百分比)
- max-width(长度和百分比)
- min-height(长度和百分比)
- min-width(长度和百分比)
- opacity(数字)
- outline-color(颜色)
- outline-offset(整数)
- outline-width(长度)
- padding-bottom(长度)
- padding-left(长度)
- padding-right(长度)
- padding-top(长度)
- right(长度和百分比)
- text-indent(长度和百分比)
- text-shadow(阴影)
- top(长度和百分比)
- vertical-align(关键字、长度和百分比)
- visibility(可见性)
- width(长度和百分比)
- word-spacing(长度和百分比)
- z-index(整数)
- zoom(数字)
SVG
SVG 对象的属性在 SVG 规范中定义为animatable:true 时是可动画的:http://www.w3.org/TR/SVG/struct.html。
声明
要在 CSS 文件中声明过渡,只需编写以下代码。
69.transition-property: all; 70.transition-duration: 0.5s; 71.transition-timing-function: ease; 72.transition-delay: 0s;
此声明定义了任何属性的任何更新将在 0.5 秒内完成(而不是立即完成)。
您也可以在每个属性的基础上定义您的过渡。
73.transition-property: opacity left top; 74.transition-duration: 0.5s 0.8s 0.1s; 75.transition-timing-function: ease linear ease; 76.transition-delay: 0s 0s 1s;
最后,您可以使用简写属性“transition”在一行中定义您需要的所有内容。
77.transition: all 0.5s ease 0s;
在此简写版本中,您可以指定任意数量的属性,用逗号分隔。
78.transition: opacity 0.5s ease 0s, left 0.8s linear 0s;
当目标对象的属性更新时,将触发过渡。更新可以通过JavaScript完成,或者通过 CSS3 分配新的类到标签来完成。
例如,使用 IE10,如果您有以下 CSS3 声明。
79.-ms-transition-property: opacity left top; 80.-ms-transition-duration: 0.5s 0.8s 0.5s; 81.-ms-transition-timing-function: ease linear ease;
当您更新标签的透明度时,当前值将在 0.5 秒内平滑过渡到新值,并使用 ease 缓动函数(这会产生平滑的动画)。
非线性过渡
“transition-timing-function”行定义了过渡将不是线性的,而是使用缓动函数来产生非线性动画。
基本上,CSS3 过渡将使用三次贝塞尔曲线,通过计算其持续时间内的不同速度来平滑过渡。
支持以下函数:
- linear:匀速。
- cubic-bezier:速度将根据由两个控制点 P0 和 P1 定义的三次贝塞尔曲线进行计算(因此您需要在这里定义 4 个值:P0x、P0y 和 P1x、P1y)。
- ease:速度将使用 cubic-bezier(0.25, 0.1, 0.25, 1) 计算。
- ease-in:速度将使用 cubic-bezier(0.42, 0, 1, 1) 计算。
- ease-inout:速度将使用 cubic-bezier(0.42, 0, 0.58, 1) 计算。
- ease-out:速度将使用 cubic-bezier(0, 0, 0.58, 1) 计算。
这是一个模拟器(当然是使用SVG)来展示每个缓动函数的影响。
<p>您的浏览器不支持 iframe。</p> 点击此处显示演示:<a href="http://www.catuhe.com/msdn/transitions/easingfunctions.htm">http://www.catuhe.com/msdn/transitions/easingfunctions.htm</a>
该模拟器是用纯 JavaScript 代码编写的,以便于理解函数。
88.TRANSITIONSHELPER.computeCubicBezierCurveInterpolation = function (t, x1, y1, x2, y2) { 89.// Extract X (which is equal to time here) 90.var f0 = 1 - 3 * x2 + 3 * x1; 91.var f1 = 3 * x2 - 6 * x1; 92.var f2 = 3 * x1; 93. 94.var refinedT = t; 95.for (var i = 0; i < 5; i++) { 96.var refinedT2 = refinedT * refinedT; 97.var refinedT3 = refinedT2 * refinedT; 98. 99.var x = f0 * refinedT3 + f1 * refinedT2 + f2 * refinedT; 100. var slope = 1.0 / (3.0 * f0 * refinedT2 + 2.0 * f1 * refinedT + f2); 101. refinedT -= (x - t) * slope; 102. refinedT = Math.min(1, Math.max(0, refinedT)); 103. } 104. 105. // Resolve cubic bezier for the given x 106. return 3 * Math.pow(1 - refinedT, 2) * refinedT * y1 + 107. 3 * (1 - refinedT) * Math.pow(refinedT, 2) * y2 + 108. Math.pow(refinedT, 3); 109. };
此代码是基于此定义的三次贝塞尔函数实现,您可以在此处找到模拟器的源代码:此处。
延迟
“transition-delay”行定义了属性更新与过渡开始之间的时间延迟。
事件
过渡结束时会触发一个事件:“TransitionEnd”。根据您的浏览器,正确的名称将是:
- Chrome 和 Safari:webkitTransitionEnd
- Firefox:mozTransitionEnd
- Opera:oTransitionEnd
- Internet Explorer:MSTransitionEnd
事件将为您提供以下信息:
- propertyName:正在动画的属性名称。
- elapsedTime:过渡已运行的时间(以秒为单位)。
以下是 IE10 的用法示例:116. block.addEventListener("MSTransitionEnd", onTransitionEvent);
更多关于 CSS3 过渡的内容
我可以提出两个主要原因来说明 CSS3 过渡为何真正有用:
- 硬件加速:CSS3 过渡直接在 GPU(可用时)上处理,并产生更平滑的结果。这对于计算能力非常有限的移动设备来说非常重要。
- 代码与设计的更好分离:对我而言,开发人员不应了解动画或与设计相关的任何内容。同样,设计师/艺术家也不应了解 JavaScript。这就是为什么 CSS3 过渡如此有趣,因为设计师可以在 CSS 中描述所有过渡,而无需开发人员的参与。
支持和回退
自 PP3 起,IE10(您可以在此处使用 Windows "8" Developer Preview 下载:此处)支持 CSS3 过渡。
此报告由https://caniuse.cn/#search=CSS3 transitions 生成。
当然,由于规范尚未完成(工作草案),您必须使用供应商前缀,例如 –ms-、–moz-、–webkit-、–o-。
显然,我们需要提供一个透明的解决方案来支持所有类型的浏览器。最好的方法是开发一个可以检测 CSS3 过渡支持的 API。如果浏览器不支持该功能,我们将回退到一些 JavaScript 代码。
如果您依赖过渡来实现网站功能,那么支持回退方法非常重要。如果您不想这样做,您应该考虑仅将过渡用于设计增强。 在这种情况下,网站仍将正常运行,但只有支持的浏览器才能提供完整的体验。我们称之为“渐进增强”,即浏览器越强大,获得的特性越多。
没有 CSS3 过渡的过渡
因此,为了能够支持 CSS3 过渡的回退,我们将开发一个小型工具包,通过代码提供过渡。
首先,我们将为我们的命名空间创建一个容器对象。
119. var TRANSITIONSHELPER = TRANSITIONSHELPER || {}; 120. 121. TRANSITIONSHELPER.tickIntervalID = 0; 122. 123. TRANSITIONSHELPER.easingFunctions = { 124. linear:0, 125. ease:1, 126. easein:2, 127. easeout:3, 128. easeinout:4, 129. custom:5 130. }; 131. 132. TRANSITIONSHELPER.currentTransitions = [];
为了支持相同级别的缓动函数,我们必须声明一个包含所有必需字段的“枚举”。
该工具包基于一个每 17ms 调用一次的函数(以实现 60 fps 的动画)。该函数将遍历活动过渡的集合。对于每个过渡,代码将根据当前值和目标值来评估下一个值。
我们需要一些方便的函数来提取属性值和使用的单位。
133. TRANSITIONSHELPER.extractValue = function (string) { 134. try { 135. var result = parseFloat(string); 136. 137. if (isNaN(result)) { 138. return 0; 139. } 140. 141. return result; 142. } catch (e) { 143. return 0; 144. } 145. }; 146. 147. TRANSITIONSHELPER.extractUnit = function (string) { 148. 149. // if value is empty we assume that it is px 150. if (string == "") { 151. return "px"; 152. } 153. 154. var value = TRANSITIONSHELPER.extractValue(string); 155. var unit = string.replace(value, ""); 156. 157. return unit; 158. };
主函数将处理活动的过渡,并将调用三次贝塞尔函数来评估当前值。
159. TRANSITIONSHELPER.tick = function () { 160. // Processing transitions 161. for (var index = 0; index < TRANSITIONSHELPER.currentTransitions.length; index++) { 162. var transition = TRANSITIONSHELPER.currentTransitions[index]; 163. 164. // compute new value 165. var currentDate = (new Date).getTime(); 166. var diff = currentDate - transition.startDate; 167. 168. var step = diff / transition.duration; 169. var offset = 1; 170. 171. // Timing function 172. switch (transition.ease) { 173. case TRANSITIONSHELPER.easingFunctions.linear: 174. offset = TRANSITIONSHELPER.computeCubicBezierCurveInterpolation(step, 0, 0, 1.0, 1.0); 175. break; 176. case TRANSITIONSHELPER.easingFunctions.ease: 177. offset = TRANSITIONSHELPER.computeCubicBezierCurveInterpolation(step, 0.25, 0.1, 0.25, 1.0); 178. break; 179. case TRANSITIONSHELPER.easingFunctions.easein: 180. offset = TRANSITIONSHELPER.computeCubicBezierCurveInterpolation(step, 0.42, 0, 1.0, 1.0); 181. break; 182. case TRANSITIONSHELPER.easingFunctions.easeout: 183. offset = TRANSITIONSHELPER.computeCubicBezierCurveInterpolation(step, 0, 0, 0.58, 1.0); 184. break; 185. case TRANSITIONSHELPER.easingFunctions.easeinout: 186. offset = TRANSITIONSHELPER.computeCubicBezierCurveInterpolation(step,0.42, 0, 0.58, 1.0); 187. break; 188. case TRANSITIONSHELPER.easingFunctions.custom: 189. offset = TRANSITIONSHELPER.computeCubicBezierCurveInterpolation(step, transition.customEaseP1X, transition.customEaseP1Y, transition.customEaseP2X, transition.customEaseP2Y); 190. break; 191. } 192. 193. offset *= (transition.finalValue - transition.originalValue); 194. 195. var unit = TRANSITIONSHELPER.extractUnit(transition.target.style[transition.property]); 196. var currentValue = transition.originalValue + offset; 197. 198. transition.currentDate = currentDate; 199. 200. // Dead transition? 201. if (currentDate >= transition.startDate + transition.duration) { 202. currentValue = transition.finalValue; // Clamping 203. TRANSITIONSHELPER.currentTransitions.splice(index, 1); // Removing transition 204. index--; 205. 206. // Completion event 207. if (transition.onCompletion) { 208. transition.onCompletion({propertyName:transition.property,elapsedTime:transition.duration}); 209. } 210. } 211. 212. // Affect it 213. transition.target.style[transition.property] = currentValue + unit; 214. } 215. };
该工具包的当前版本仅支持数值,但如果您想为复杂值(如颜色)设置动画,只需将它们分解为简单值即可。
使用以下代码将过渡注册到系统中。
216. TRANSITIONSHELPER.transition = function (target, property, newValue, duration, ease, customEaseP1X, customEaseP1Y, customEaseP2X, customEaseP2Y, onCompletion) { 217. 218. // Create a new transition 219. var transition = { 220. target: target, 221. property: property, 222. finalValue: newValue, 223. originalValue: TRANSITIONSHELPER.extractValue(target.style[property]), 224. duration: duration, 225. startDate: (new Date).getTime(), 226. currentDate: (new Date).getTime(), 227. ease:ease, 228. customEaseP1X:customEaseP1X, 229. customEaseP2X:customEaseP2X, 230. customEaseP1Y: customEaseP1Y, 231. customEaseP2Y: customEaseP2Y, 232. onCompletion: onCompletion 233. }; 234. 235. // Launching the tick service if required 236. if (TRANSITIONSHELPER.tickIntervalID == 0) { 237. TRANSITIONSHELPER.tickIntervalID = setInterval(TRANSITIONSHELPER.tick, 17); 238. } 239. 240. // Remove previous transitions on same property and target 241. for (var index = 0; index < TRANSITIONSHELPER.currentTransitions.length; index++) { 242. var temp = TRANSITIONSHELPER.currentTransitions[index]; 243. 244. if (temp.target === transition.target && temp.property === transition.property) { 245. TRANSITIONSHELPER.currentTransitions.splice(index, 1); 246. index--; 247. } 248. } 249. 250. // Register 251. if (transition.originalValue != transition.finalValue) { 252. TRANSITIONSHELPER.currentTransitions.push(transition); 253. } 254. };
当第一个过渡激活时,“tick
”函数将启动。
最后,您只需要使用modernizr 来检测当前浏览器是否支持 CSS3 过渡。如果不支持,您可以回退到我们的工具包。
TransitionsHelper 的代码可以在此处下载:http://www.catuhe.com/msdn/transitions/transitionshelper.js
例如,在我的拼图游戏中,以下代码用于为单元格设置动画。
255. if (!PUZZLE.isTransitionsSupported) { 256. TRANSITIONSHELPER.transition(block.div, "top", block.x * totalSize + offset, 500, TRANSITIONSHELPER.easingFunctions.ease); 257. TRANSITIONSHELPER.transition(block.div, "left", block.y * totalSize + offset, 500, TRANSITIONSHELPER.easingFunctions.ease); 258. } 259. else { 260. block.div.style.top = (block.x * totalSize + offset) + "px"; 261. block.div.style.left = (block.y * totalSize + offset) + "px"; 262. }
我们可以注意到,如果支持 CSS3 过渡,我也可以采用另一种方式来为我的单元格设置动画:我可以定义一个 CSS3 类的集合,其中包含预定义的 left 和 top 值(每个单元格一个),以将它们应用于正确的单元格。
一些框架和工具包已经存在以支持软件过渡。
- jQuery.transition.js:http://louisremi.github.com/jquery.transition.js/test/index.html
- jQUery-Animate-Enhanced:https://github.com/benbarnett/jQuery-Animate-Enhanced
顺便说一句,您也可以使用 jQuery 的老式 animate()
方法。
结论
正如我们所见,CSS3 过渡是为您的项目添加动画的简单方法。通过使用一些过渡来更改值,您可以创建一个更具响应性的应用程序。
顺便说一句,如果您想实现 JavaScript 回退,有两种解决方案:
- 您可以全部在 JavaScript 端进行操作,如果您检测到 CSS3 过渡支持,您将在页面中注入 CSS3 声明。
- 或者您可以使用标准方法(在 CSS 文件中使用真正的 CSS3 声明),并在 JavaScript 中检测回退需求。对我来说,这是更好的选择,因为回退应该是可选的,而不是主要内容。在不久的将来,所有浏览器都将支持 CSS3 过渡,届时您只需要删除回退代码。此外,这是一种更好的方式,可以让所有 CSS创意团队控制,而不是代码部分。
深入研究
- David Rousset 的 CSS3 动画博客:http://blogs.msdn.com/b/davrous/archive/2011/12/06/introduction-to-css3-animations.aspx
- CSS3 过渡规范:http://www.w3.org/TR/css3-transitions/
- IE Test Drive CSS3 过渡:http://ie.microsoft.com/testdrive/Graphics/hands-on-css3/hands-on_transitions.htm
- 其他有用的帖子