- CSS3 Animation动画的十二原则
作为前端的设计师和工程师,我们用 CSS 去做样式、定位并创建出好看的网站。我们经常用 CSS 去添加页面的运动过渡效果甚至动画,但我们经常做的不过如此。 [代码] 动效是一个有助于访客和用户理解我们设计的强有力工具。这里有些原则能最大限度地应用在我们的工作中。 迪士尼经过基础工作练习的长时间累积,在 1981 年出版的 The Illusion of Life: Disney Animation 一书中发表了动画的十二个原则 ([] (https://en.wikipedia.org/wiki/12_basic_principles_of_animation)) 。这些原则描述了动画能怎样用于让观众相信自己沉浸在现实世界中。 [代码] 在本文中,我会逐个介绍这十二个原则,并讨论它们怎样运用在网页中。你能在 Codepen 找到它们[] (https://codepen.io/collection/AxKOdY/)。 挤压和拉伸 (Squash and stretch) [图片] 这是物体存在质量且运动时质量保持不变的概念。当一个球在弹跳时,碰击到地面会变扁,恢复的时间会越来越短。 [代码] 创建对象的时候最有用的方法是参照实物,比如人、时钟和弹性球。 当它和网页元件一起工作时可能会忽略这个原则。DOM 对象不一定和实物相关,它会按需要在屏幕上缩放。例如,一个按钮会变大并变成一个信息框,或者错误信息会出现和消失。 尽管如此,挤压和伸缩效果可以为一个对象增加实物的感觉。甚至一些形状上的小变化就可以创造出细微但抢眼的效果。 HTML [代码] [代码] <h1>Principle 1: Squash and stretch</h1> <h2><a href="https://cssanimation.rocks/principles/" target="_parent">Animation Principles for the Web</h2> <article class="principle one"> <div class="shape"></div> <div class="surface"></div> </article> [代码] CSS [代码].one .shape { animation: one 4s infinite ease-out; } .one .surface { background: #000; height: 10em; width: 1em; position: absolute; top: calc(50% - 4em); left: calc(50% + 10em); } @keyframes one { 0%, 15% { opacity: 0; } 15%, 25% { transform: none; animation-timing-function: cubic-bezier(1,-1.92,.95,.89); width: 4em; height: 4em; top: calc(50% - 2em); left: calc(50% - 2em); opacity: 1; } 35%, 45% { transform: translateX(8em); height: 6em; width: 2em; top: calc(50% - 3em); animation-timing-function: linear; opacity: 1; } 70%, 100% { transform: translateX(8em) translateY(5em); height: 6em; width: 2em; top: calc(50% - 3em); opacity: 0; } } body { margin: 0; background: #e9b59f; font-family: HelveticaNeue, Arial, Sans-serif; color: #fff; } h1 { position: absolute; top: 0; left: 0; right: 0; text-align: center; font-weight: 300; } h2 { font-size: 0.75em; position: absolute; bottom: 0; left: 0; right: 0; text-align: center; } a { text-decoration: none; color: #333; } .principle { width: 100%; height: 100vh; position: relative; } .shape { background: #2d97db; border: 1em solid #fff; width: 4em; height: 4em; position: absolute; top: calc(50% - 2em); left: calc(50% - 2em); } [代码] 预备动作 (Anticipation) [图片] 运动不倾向于突然发生。在现实生活中,无论是一个球在掉到桌子前就开始滚动,或是一个人屈膝准备起跳,运动通常有着某种事先的累积。 [代码] 我们能用它去让我们的过渡动画显得更逼真。预备动作可以是一个细微的反弹,帮人们理解什么对象将在屏幕中发生变化并留下痕迹。 例如,悬停在一个元件上时可以在它变大前稍微缩小,在初始列表中添加额外的条目来介绍其它条目的移除方法。 [代码] HTML [代码]<h1>Principle 2: Anticipation</h1> <h2><a href="https://cssanimation.rocks/principles/" target="_parent">Animation Principles for the Web</h2> <article class="principle two"> <div class="shape"></div> <div class="surface"></div> </article> [代码] CSS [代码].two .shape { animation: two 5s infinite ease-out; transform-origin: 50% 7em; } .two .surface { background: #000; width: 8em; height: 1em; position: absolute; top: calc(50% + 4em); left: calc(50% - 3em); } @keyframes two { 0%, 15% { opacity: 0; transform: none; } 15%, 25% { opacity: 1; transform: none; animation-timing-function: cubic-bezier(.5,.05,.91,.47); } 28%, 38% { transform: translateX(-2em); } 40%, 45% { transform: translateX(-4em); } 50%, 52% { transform: translateX(-4em) rotateZ(-20deg); } 70%, 75% { transform: translateX(-4em) rotateZ(-10deg); } 78% { transform: translateX(-4em) rotateZ(-24deg); opacity: 1; } 86%, 100% { transform: translateX(-6em) translateY(4em) rotateZ(-90deg); opacity: 0; } } /* General styling */ body { margin: 0; background: #e9b59f; font-family: HelveticaNeue, Arial, Sans-serif; color: #fff; } h1 { position: absolute; top: 0; left: 0; right: 0; text-align: center; font-weight: 300; } h2 { font-size: 0.75em; position: absolute; bottom: 0; left: 0; right: 0; text-align: center; } a { text-decoration: none; color: #333; } .principle { width: 100%; height: 100vh; position: relative; } .shape { background: #2d97db; border: 1em solid #fff; width: 4em; height: 4em; position: absolute; top: calc(50% - 2em); left: calc(50% - 2em); } [代码] 演出布局 (Staging) [图片] 演出布局是确保对象在场景中得以聚焦,让场景中的其它对象和视觉在主动画发生的地方让位。这意味着要么把主动画放到突出的位置,要么模糊其它元件来让用户专注于看他们需要看的东西。 [代码] 在网页方面,一种方法是用 model 覆盖在某些内容上。在现有页面添加一个遮罩并把那些主要关注的内容前置展示。 另一种方法是用动作。当很多对象在运动,你很难知道哪些值得关注。如果其它所有的动作停止,只留一个在运动,即使动得很微弱,这都可以让对象更容易被察觉。 [代码] 还有一种方法是做一个晃动和闪烁的按钮来简单地建议用户比如他们可能要保存文档。屏幕保持静态,所以再细微的动作也会突显出来。 HTML [代码]<h1>Principle 3: Staging</h1> <h2><a href="https://cssanimation.rocks/principles/" target="_parent">Animation Principles for the Web</h2> <article class="principle three"> <div class="shape a"></div> <div class="shape b"></div> <div class="shape c"></div> </article> [代码] CSS [代码].three .shape.a { transform: translateX(-12em); } .three .shape.c { transform: translateX(12em); } .three .shape.b { animation: three 5s infinite ease-out; transform-origin: 0 6em; } .three .shape.a, .three .shape.c { animation: threeb 5s infinite linear; } @keyframes three { 0%, 10% { transform: none; animation-timing-function: cubic-bezier(.57,-0.5,.43,1.53); } 26%, 30% { transform: rotateZ(-40deg); } 32.5% { transform: rotateZ(-38deg); } 35% { transform: rotateZ(-42deg); } 37.5% { transform: rotateZ(-38deg); } 40% { transform: rotateZ(-40deg); } 42.5% { transform: rotateZ(-38deg); } 45% { transform: rotateZ(-42deg); } 47.5% { transform: rotateZ(-38deg); animation-timing-function: cubic-bezier(.57,-0.5,.43,1.53); } 58%, 100% { transform: none; } } @keyframes threeb { 0%, 20% { filter: none; } 40%, 50% { filter: blur(5px); } 65%, 100% { filter: none; } } /* General styling */ body { margin: 0; background: #e9b59f; font-family: HelveticaNeue, Arial, Sans-serif; color: #fff; } h1 { position: absolute; top: 0; left: 0; right: 0; text-align: center; font-weight: 300; } h2 { font-size: 0.75em; position: absolute; bottom: 0; left: 0; right: 0; text-align: center; } a { text-decoration: none; color: #333; } .principle { width: 100%; height: 100vh; position: relative; } .shape { background: #2d97db; border: 1em solid #fff; width: 4em; height: 4em; position: absolute; top: calc(50% - 2em); left: calc(50% - 2em); } [代码] 连续运动和姿态对应 (Straight-Ahead Action and Pose-to-Pose) [图片] 连续运动是绘制动画的每一帧,姿态对应是通常由一个 assistant 在定义一系列关键帧后填充间隔。 [代码] 大多数网页动画用的是姿态对应:关键帧之间的过渡可以通过浏览器在每个关键帧之间的插入尽可能多的帧使动画流畅。 [代码] 有一个例外是定时功能step。通过这个功能,浏览器 “steps” 可以把尽可能多的无序帧串清晰。你可以用这种方式绘制一系列图片并让浏览器按顺序显示出来,这开创了一种逐帧动画的风格。 HTML [代码]<h1>Principle 4: Straight Ahead Action and Pose to Pose</h1> <h2><a href="https://cssanimation.rocks/principles/" target="_parent">Animation Principles for the Web</h2> <article class="principle four"> <div class="shape a"></div> <div class="shape b"></div> </article> [代码] CSS [代码].four .shape.a { left: calc(50% - 8em); animation: four 6s infinite cubic-bezier(.57,-0.5,.43,1.53); } .four .shape.b { left: calc(50% + 8em); animation: four 6s infinite steps(1); } @keyframes four { 0%, 10% { transform: none; } 26%, 30% { transform: rotateZ(-45deg) scale(1.25); } 40% { transform: rotateZ(-45deg) translate(2em, -2em) scale(1.8); } 50%, 75% { transform: rotateZ(-45deg) scale(1.1); } 90%, 100% { transform: none; } } /* General styling */ body { margin: 0; background: #e9b59f; font-family: HelveticaNeue, Arial, Sans-serif; color: #fff; } h1 { position: absolute; top: 0; left: 0; right: 0; text-align: center; font-weight: 300; } h2 { font-size: 0.75em; position: absolute; bottom: 0; left: 0; right: 0; text-align: center; } a { text-decoration: none; color: #333; } .principle { width: 100%; height: 100vh; position: relative; } .shape { background: #2d97db; border: 1em solid #fff; width: 4em; height: 4em; position: absolute; top: calc(50% - 2em); left: calc(50% - 2em); } [代码] 跟随和重叠动作 (Follow Through and Overlapping Action) [图片] 事情并不总在同一时间发生。当一辆车从急刹到停下,车子会向前倾、有烟从轮胎冒出来、车里的司机继续向前冲。 [代码] 这些细节是跟随和重叠动作的例子。它们在网页中能被用作帮助强调什么东西被停止,并不会被遗忘。例如一个条目可能在滑动时稍滑微远了些,但它自己会纠正到正确位置。 要创造一个重叠动作的感觉,我们可以让元件以稍微不同的速度移动到每处。这是一种在 iOS 系统的视窗 (View) 过渡中被运用得很好的方法。一些按钮和元件以不同速率运动,整体效果会比全部东西以相同速率运动要更逼真,并留出时间让访客去适当理解变化。 [代码] 在网页方面,这可能意味着让过渡或动画的效果以不同速度来运行。 HTML [代码]<h1>Principle 5: Follow Through and Overlapping Action</h1> <h2><a href="https://cssanimation.rocks/principles/" target="_parent">Animation Principles for the Web</h2> <article class="principle five"> <div class="shape-container"> <div class="shape"></div> </div> </article> [代码] CSS [代码].five .shape { animation: five 4s infinite cubic-bezier(.64,-0.36,.1,1); position: relative; left: auto; top: auto; } .five .shape-container { animation: five-container 4s infinite cubic-bezier(.64,-0.36,.1,2); position: absolute; left: calc(50% - 4em); top: calc(50% - 4em); } @keyframes five { 0%, 15% { opacity: 0; transform: translateX(-12em); } 15%, 25% { transform: translateX(-12em); opacity: 1; } 85%, 90% { transform: translateX(12em); opacity: 1; } 100% { transform: translateX(12em); opacity: 0; } } @keyframes five-container { 0%, 35% { transform: none; } 50%, 60% { transform: skewX(20deg); } 90%, 100% { transform: none; } } /* General styling */ body { margin: 0; background: #e9b59f; font-family: HelveticaNeue, Arial, Sans-serif; color: #fff; } h1 { position: absolute; top: 0; left: 0; right: 0; text-align: center; font-weight: 300; } h2 { font-size: 0.75em; position: absolute; bottom: 0; left: 0; right: 0; text-align: center; } a { text-decoration: none; color: #333; } .principle { width: 100%; height: 100vh; position: relative; } .shape { background: #2d97db; border: 1em solid #fff; width: 4em; height: 4em; position: absolute; top: calc(50% - 2em); left: calc(50% - 2em); } [代码] 缓入缓出 (Slow In and Slow Out) [图片] 对象很少从静止状态一下子加速到最大速度,它们往往是逐步加速并在停止前变慢。没有加速和减速,动画感觉就像机器人。 [代码] 在 CSS 方面,缓入缓出很容易被理解,在一个动画过程中计时功能是一种描述变化速率的方式。 [代码] 使用计时功能,动画可以由慢加速 (ease-in)、由快减速 (ease-out),或者用贝塞尔曲线做出更复杂的效果。 HTML [代码]<h1>Principle 6: Slow in and Slow out</h1> <h2><a href="https://cssanimation.rocks/principles/" target="_parent">Animation Principles for the Web</h2> <article class="principle six"> <div class="shape a"></div> </article> [代码] CSS [代码].six .shape { animation: six 3s infinite cubic-bezier(0.5,0,0.5,1); } @keyframes six { 0%, 5% { transform: translate(-12em); } 45%, 55% { transform: translate(12em); } 95%, 100% { transform: translate(-12em); } } /* General styling */ body { margin: 0; background: #e9b59f; font-family: HelveticaNeue, Arial, Sans-serif; color: #fff; } h1 { position: absolute; top: 0; left: 0; right: 0; text-align: center; font-weight: 300; } h2 { font-size: 0.75em; position: absolute; bottom: 0; left: 0; right: 0; text-align: center; } a { text-decoration: none; color: #333; } .principle { width: 100%; height: 100vh; position: relative; } .shape { background: #2d97db; border: 1em solid #fff; width: 4em; height: 4em; position: absolute; top: calc(50% - 2em); left: calc(50% - 2em); } [代码] 弧线运动 (Arc) [图片] 虽然对象是更逼真了,当它们遵循「缓入缓出」的时候它们很少沿直线运动——它们倾向于沿弧线运动。 我们有几种 CSS 的方式来实现弧线运动。一种是结合多个动画,比如在弹力球动画里,可以让球上下移动的同时让它右移,这时候球的显示效果就是沿弧线运动。 HTML [代码]<h1>Principle 7: Arc (1)</h1> <h2><a href="https://cssanimation.rocks/principles/" target="_parent">Animation Principles for the Web</h2> <article class="principle sevena"> <div class="shape-container"> <div class="shape a"></div> </div> </article> [代码] CSS [代码].sevena .shape-container { animation: move-right 6s infinite cubic-bezier(.37,.55,.49,.67); position: absolute; left: calc(50% - 4em); top: calc(50% - 4em); } .sevena .shape { animation: bounce 6s infinite linear; border-radius: 50%; position: relative; left: auto; top: auto; } @keyframes move-right { 0% { transform: translateX(-20em); opacity: 1; } 80% { opacity: 1; } 90%, 100% { transform: translateX(20em); opacity: 0; } } @keyframes bounce { 0% { transform: translateY(-8em); animation-timing-function: cubic-bezier(.51,.01,.79,.02); } 15% { transform: translateY(8em); animation-timing-function: cubic-bezier(.19,1,.7,1); } 25% { transform: translateY(-4em); animation-timing-function: cubic-bezier(.51,.01,.79,.02); } 32.5% { transform: translateY(8em); animation-timing-function: cubic-bezier(.19,1,.7,1); } 40% { transform: translateY(0em); animation-timing-function: cubic-bezier(.51,.01,.79,.02); } 45% { transform: translateY(8em); animation-timing-function: cubic-bezier(.19,1,.7,1); } 50% { transform: translateY(3em); animation-timing-function: cubic-bezier(.51,.01,.79,.02); } 56% { transform: translateY(8em); animation-timing-function: cubic-bezier(.19,1,.7,1); } 60% { transform: translateY(6em); animation-timing-function: cubic-bezier(.51,.01,.79,.02); } 64% { transform: translateY(8em); animation-timing-function: cubic-bezier(.19,1,.7,1); } 66% { transform: translateY(7.5em); animation-timing-function: cubic-bezier(.51,.01,.79,.02); } 70%, 100% { transform: translateY(8em); animation-timing-function: cubic-bezier(.19,1,.7,1); } } /* General styling */ body { margin: 0; background: #e9b59f; font-family: HelveticaNeue, Arial, Sans-serif; color: #fff; } h1 { position: absolute; top: 0; left: 0; right: 0; text-align: center; font-weight: 300; } h2 { font-size: 0.75em; position: absolute; bottom: 0; left: 0; right: 0; text-align: center; } a { text-decoration: none; color: #333; } .principle { width: 100%; height: 100vh; position: relative; } .shape { background: #2d97db; border: 1em solid #fff; width: 4em; height: 4em; position: absolute; top: calc(50% - 2em); left: calc(50% - 2em); } [代码] [图片] 另外一种是旋转元件,我们可以设置一个在对象之外的原点来作为它的旋转中心。当我们旋转这个对象,它看上去就是沿着弧线运动。 HTML [代码]<h1>Principle 7: Arc (2)</h1> <h2><a href="https://cssanimation.rocks/principles/" target="_parent">Animation Principles for the Web</h2> <article class="principle sevenb"> <div class="shape a"></div> <div class="shape b"></div> </article> [代码] CSS [代码].sevenb .shape.a { animation: sevenb 3s infinite linear; top: calc(50% - 2em); left: calc(50% - 9em); transform-origin: 10em 50%; } .sevenb .shape.b { animation: sevenb 6s infinite linear reverse; background-color: yellow; width: 2em; height: 2em; left: calc(50% - 1em); top: calc(50% - 1em); } @keyframes sevenb { 100% { transform: rotateZ(360deg); } } /* General styling */ body { margin: 0; background: #e9b59f; font-family: HelveticaNeue, Arial, Sans-serif; color: #fff; } h1 { position: absolute; top: 0; left: 0; right: 0; text-align: center; font-weight: 300; } h2 { font-size: 0.75em; position: absolute; bottom: 0; left: 0; right: 0; text-align: center; } a { text-decoration: none; color: #333; } .principle { width: 100%; height: 100vh; position: relative; } .shape { background: #2d97db; border: 1em solid #fff; width: 4em; height: 4em; position: absolute; top: calc(50% - 2em); left: calc(50% - 2em); } [代码] 次要动作 (Secondary Action) [图片] 虽然主动画正在发生,次要动作可以增强它的效果。这就好比某人在走路的时候摆动手臂和倾斜脑袋,或者弹性球弹起的时候扬起一些灰尘。 在网页方面,当主要焦点出现的时候就可以开始执行次要动作,比如拖拽一个条目到列表中间。 HTML [代码]<h1>Principle 8: Secondary Action</h1> <h2><a href="https://cssanimation.rocks/principles/" target="_parent">Animation Principles for the Web</h2> <article class="principle eight"> <div class="shape a"></div> <div class="shape b"></div> <div class="shape c"></div> </article> [代码] CSS [代码].eight .shape.a { transform: translateX(-6em); animation: eight-shape-a 4s cubic-bezier(.57,-0.5,.43,1.53) infinite; } .eight .shape.b { top: calc(50% + 6em); opacity: 0; animation: eight-shape-b 4s linear infinite; } .eight .shape.c { transform: translateX(6em); animation: eight-shape-c 4s cubic-bezier(.57,-0.5,.43,1.53) infinite; } @keyframes eight-shape-a { 0%, 50% { transform: translateX(-5.5em); } 70%, 100% { transform: translateX(-10em); } } @keyframes eight-shape-b { 0% { transform: none; } 20%, 30% { transform: translateY(-1.5em); opacity: 1; animation-timing-function: cubic-bezier(.57,-0.5,.43,1.53); } 32% { transform: translateY(-1.25em); opacity: 1; } 34% { transform: translateY(-1.75em); opacity: 1; } 36%, 38% { transform: translateY(-1.25em); opacity: 1; } 42%, 60% { transform: translateY(-1.5em); opacity: 1; } 75%, 100% { transform: translateY(-8em); opacity: 1; } } @keyframes eight-shape-c { 0%, 50% { transform: translateX(5.5em); } 70%, 100% { transform: translateX(10em); } } /* General styling */ body { margin: 0; background: #e9b59f; font-family: HelveticaNeue, Arial, Sans-serif; color: #fff; } h1 { position: absolute; top: 0; left: 0; right: 0; text-align: center; font-weight: 300; } h2 { font-size: 0.75em; position: absolute; bottom: 0; left: 0; right: 0; text-align: center; } a { text-decoration: none; color: #333; } .principle { width: 100%; height: 100vh; position: relative; } .shape { background: #2d97db; border: 1em solid #fff; width: 4em; height: 4em; position: absolute; top: calc(50% - 2em); left: calc(50% - 2em); } [代码] 时间节奏 (Timing) [图片] 动画的时间节奏是需要多久去完成,它可以被用来让看起来很重的对象做很重的动画,或者用在添加字符的动画中。 [代码] 这在网页上可能只要简单调整 animation-duration 或 transition-duration 值。 [代码] 这很容易让动画消耗更多时间,但调整时间节奏可以帮动画的内容和交互方式变得更出众。 HTML [代码]<h1>Principle 9: Timing</h1> <h2><a href="https://cssanimation.rocks/principles/" target="_parent">Animation Principles for the Web</h2> <article class="principle nine"> <div class="shape a"></div> <div class="shape b"></div> </article> [代码] CSS [代码].nine .shape.a { animation: nine 4s infinite cubic-bezier(.93,0,.67,1.21); left: calc(50% - 12em); transform-origin: 100% 6em; } .nine .shape.b { animation: nine 2s infinite cubic-bezier(1,-0.97,.23,1.84); left: calc(50% + 2em); transform-origin: 100% 100%; } @keyframes nine { 0%, 10% { transform: translateX(0); } 40%, 60% { transform: rotateZ(90deg); } 90%, 100% { transform: translateX(0); } } /* General styling */ body { margin: 0; background: #e9b59f; font-family: HelveticaNeue, Arial, Sans-serif; color: #fff; } h1 { position: absolute; top: 0; left: 0; right: 0; text-align: center; font-weight: 300; } h2 { font-size: 0.75em; position: absolute; bottom: 0; left: 0; right: 0; text-align: center; } a { text-decoration: none; color: #333; } .principle { width: 100%; height: 100vh; position: relative; } .shape { background: #2d97db; border: 1em solid #fff; width: 4em; height: 4em; position: absolute; top: calc(50% - 2em); left: calc(50% - 2em); } [代码] 夸张手法 (Exaggeration) [图片] 夸张手法在漫画中是最常用来为某些动作刻画吸引力和增加戏剧性的,比如一只狼试图把自己的喉咙张得更开地去咬东西可能会表现出更恐怖或者幽默的效果。 在网页中,对象可以通过上下滑动去强调和刻画吸引力,比如在填充表单的时候生动部分会比收缩和变淡的部分更突出。 HTML [代码]<h1>Principle 10: Exaggeration</h1> <h2><a href="https://cssanimation.rocks/principles/" target="_parent">Animation Principles for the Web</h2> <article class="principle ten"> <div class="shape"></div> </article> [代码] CSS [代码].ten .shape { animation: ten 4s infinite linear; transform-origin: 50% 8em; top: calc(50% - 6em); } @keyframes ten { 0%, 10% { transform: none; animation-timing-function: cubic-bezier(.87,-1.05,.66,1.31); } 40% { transform: rotateZ(-45deg) scale(2); animation-timing-function: cubic-bezier(.16,.54,0,1.38); } 70%, 100% { transform: rotateZ(360deg) scale(1); } } /* General styling */ body { margin: 0; background: #e9b59f; font-family: HelveticaNeue, Arial, Sans-serif; color: #fff; } h1 { position: absolute; top: 0; left: 0; right: 0; text-align: center; font-weight: 300; } h2 { font-size: 0.75em; position: absolute; bottom: 0; left: 0; right: 0; text-align: center; } a { text-decoration: none; color: #333; } .principle { width: 100%; height: 100vh; position: relative; } .shape { background: #2d97db; border: 1em solid #fff; width: 4em; height: 4em; position: absolute; top: calc(50% - 2em); left: calc(50% - 2em); } [代码] 扎实的描绘 (Solid drawing) [图片] 当动画对象在三维中应该加倍注意确保它们遵循透视原则。因为人们习惯了生活在三维世界里,如果对象表现得与实际不符,会让它看起来很糟糕。 如今浏览器对三维变换的支持已经不错,这意味着我们可以在场景里旋转和放置三维对象,浏览器能自动控制它们的转换。 HTML [代码]<h1>Principle 11: Solid drawing</h1> <h2><a href="https://cssanimation.rocks/principles/" target="_parent">Animation Principles for the Web</h2> <article class="principle eleven"> <div class="shape"> <div class="container"> <span class="front"></span> <span class="back"></span> <span class="left"></span> <span class="right"></span> <span class="top"></span> <span class="bottom"></span> </div> </div> </article> [代码] CSS [代码].eleven .shape { background: none; border: none; perspective: 400px; perspective-origin: center; } .eleven .shape .container { animation: eleven 4s infinite cubic-bezier(.6,-0.44,.37,1.44); transform-style: preserve-3d; } .eleven .shape span { display: block; position: absolute; opacity: 1; width: 4em; height: 4em; border: 1em solid #fff; background: #2d97db; } .eleven .shape span.front { transform: translateZ(3em); } .eleven .shape span.back { transform: translateZ(-3em); } .eleven .shape span.left { transform: rotateY(-90deg) translateZ(-3em); } .eleven .shape span.right { transform: rotateY(-90deg) translateZ(3em); } .eleven .shape span.top { transform: rotateX(-90deg) translateZ(-3em); } .eleven .shape span.bottom { transform: rotateX(-90deg) translateZ(3em); } @keyframes eleven { 0% { opacity: 0; } 10%, 40% { transform: none; opacity: 1; } 60%, 75% { transform: rotateX(-20deg) rotateY(-45deg) translateY(4em); animation-timing-function: cubic-bezier(1,-0.05,.43,-0.16); opacity: 1; } 100% { transform: translateZ(-180em) translateX(20em); opacity: 0; } } /* General styling */ body { margin: 0; background: #e9b59f; font-family: HelveticaNeue, Arial, Sans-serif; color: #fff; } h1 { position: absolute; top: 0; left: 0; right: 0; text-align: center; font-weight: 300; } h2 { font-size: 0.75em; position: absolute; bottom: 0; left: 0; right: 0; text-align: center; } a { text-decoration: none; color: #333; } .principle { width: 100%; height: 100vh; position: relative; } .shape { background: #2d97db; border: 1em solid #fff; width: 4em; height: 4em; position: absolute; top: calc(50% - 2em); left: calc(50% - 2em); } [代码] 吸引力 (Appeal) [图片] 吸引力是艺术作品的特质,让我们与艺术家的想法连接起来。就像一个演员身上的魅力,是注重细节和动作相结合而打造吸引性的结果。 [代码] 精心制作网页上的动画可以打造出吸引力,例如 Stripe 这样的公司用了大量的动画去增加它们结账流程的可靠性。 [代码] HTML [代码]<h1>Principle 12: Appeal</h1> <h2><a href="https://cssanimation.rocks/principles/" target="_parent">Animation Principles for the Web</h2> <article class="principle twelve"> <div class="shape"> <div class="container"> <span class="item one"></span> <span class="item two"></span> <span class="item three"></span> <span class="item four"></span> </div> </div> </article> [代码] CSS [代码].twelve .shape { background: none; border: none; perspective: 400px; perspective-origin: center; } .twelve .shape .container { animation: show-container 8s infinite cubic-bezier(.6,-0.44,.37,1.44); transform-style: preserve-3d; width: 4em; height: 4em; border: 1em solid #fff; background: #2d97db; position: relative; } .twelve .item { background-color: #1f7bb6; position: absolute; } .twelve .item.one { animation: show-text 8s 0.1s infinite ease-out; height: 6%; width: 30%; top: 15%; left: 25%; } .twelve .item.two { animation: show-text 8s 0.2s infinite ease-out; height: 6%; width: 20%; top: 30%; left: 25%; } .twelve .item.three { animation: show-text 8s 0.3s infinite ease-out; height: 6%; width: 50%; top: 45%; left: 25%; } .twelve .item.four { animation: show-button 8s infinite cubic-bezier(.64,-0.36,.1,1.43); height: 20%; width: 40%; top: 65%; left: 30%; } @keyframes show-container { 0% { opacity: 0; transform: rotateX(-90deg); } 10% { opacity: 1; transform: none; width: 4em; height: 4em; } 15%, 90% { width: 12em; height: 12em; transform: translate(-4em, -4em); opacity: 1; } 100% { opacity: 0; transform: rotateX(-90deg); width: 4em; height: 4em; } } @keyframes show-text { 0%, 15% { transform: translateY(1em); opacity: 0; } 20%, 85% { opacity: 1; transform: none; } 88%, 100% { opacity: 0; transform: translateY(-1em); animation-timing-function: cubic-bezier(.64,-0.36,.1,1.43); } } @keyframes show-button { 0%, 25% { transform: scale(0); opacity: 0; } 35%, 80% { transform: none; opacity: 1; } 90%, 100% { opacity: 0; transform: scale(0); } } /* General styling */ body { margin: 0; background: #e9b59f; font-family: HelveticaNeue, Arial, Sans-serif; color: #fff; } h1 { position: absolute; top: 0; left: 0; right: 0; text-align: center; font-weight: 300; } h2 { font-size: 0.75em; position: absolute; bottom: 0; left: 0; right: 0; text-align: center; } a { text-decoration: none; color: #333; } .principle { width: 100%; height: 100vh; position: relative; } .shape { background: #2d97db; border: 1em solid #fff; width: 4em; height: 4em; position: absolute; top: calc(50% - 2em); left: calc(50% - 2em); } [代码]
2019-03-21 - [填坑手册]小程序Canvas生成海报(一)--完整流程
[图片] 海报生成示例 最近智酷君在做[小程序]canvas生成海报的项目中遇到一些棘手的问题,在网上查阅了各种资料,也踩扁了各种坑,智酷君希望把这些“填坑”经验整理一下分享出来,避免后来的兄弟重复“掉坑”。 [图片] 原型图 这是一个大致的原型图,下面来看下如何制作这个海报,以及整体的思路。 [图片] 海报生成流程 [代码片段]Canvas生成海报实战demo demo的微信路径:https://developers.weixin.qq.com/s/Q74OU3m57c9x demo的ID:Q74OU3m57c9x 如果你装了IDE工具,可以直接访问上面的demo路径 通过代码片段将demo的ID输入进去也可添加: [图片] [图片] 下面分享下主要的代码内容和“填坑现场”: 一、添加字体 https://developers.weixin.qq.com/miniprogram/dev/api/canvas/font.html [代码]canvasContext.font = value //示例 ctx.font = `normal bold 20px sans-serif`//设置字体大小,默认10 ctx.setTextAlign('left'); ctx.setTextBaseline("top"); ctx.fillText("《智酷方程式》专注研究和分享前端技术", 50, 15, 250)//绘制文本 [代码] 符合 CSS font 语法的 DOMString 字符串,至少需要提供字体大小和字体族名。默认值为 10px sans-serif 文字过长在canvas下换行问题处理(最多两行,超过“…”代替) [代码]ctx.setTextAlign('left'); ctx.setFillStyle('#000');//文字颜色:默认黑色 ctx.font = `normal bold 18px sans-serif`//设置字体大小,默认10 let canvasTitleArray = canvasTitle.split(""); let firstTitle = ""; //第一行字 let secondTitle = ""; //第二行字 for (let i = 0; i < canvasTitleArray.length; i++) { let element = canvasTitleArray[i]; let firstWidth = ctx.measureText(firstTitle).width; //console.log(ctx.measureText(firstTitle).width); if (firstWidth > 260) { let secondWidth = ctx.measureText(secondTitle).width; //第二行字数超过,变为... if (secondWidth > 260) { secondTitle += "..."; break; } else { secondTitle += element; } } else { firstTitle += element; } } //第一行文字 ctx.fillText(firstTitle, 20, 278, 280)//绘制文本 //第二行问题 if (secondTitle) { ctx.fillText(secondTitle, 20, 300, 280)//绘制文本 } [代码] 通过 ctx.measureText 这个方法可以判断文字的宽度,然后进行切割。 (一行字允许宽度为280时,判断需要写小点,比如260) 二、获取临时地址并设置图片 [代码]let mainImg = "https://demo.com/url.jpg"; wx.getImageInfo({ src: mainImg,//服务器返回的图片地址 success: function (res) { //处理图片纵横比例过大或者过小的问题!!! let h = res.height; let w = res.width; let setHeight = 280, //默认源图截取的区域 setWidth = 220; //默认源图截取的区域 if (w / h > 1.5) { setHeight = h; setWidth = parseInt(280 / 220 * h); } else if (w / h < 1) { setWidth = w; setHeight = parseInt(220 / 280 * w); } else { setHeight = h; setWidth = w; }; console.log(setWidth, setHeight) ctx.drawImage(res.path, 0, 0, setWidth, setHeight, 20, 50, 280, 220); ctx.draw(true); }, fail: function (res) { //失败回调 } }); [代码] 在开发过程中如果封面图无法按照约定的比例(280x220)给到: 那么我们就需要处理默认封面图过大或者过小的问题,大致思路是:代码中通过比较纵横比(280/220=1.27)正比例放大或者缩小原图,然后从左上切割,竟可能保证过高的图是宽度100%,过宽的图是高度100%。 在canvas中draw图片,必须是一个(相对)本地路径,我们可以通过将图片保存在本地后生成的临时路径。 微信官方提供两个API: wx.downloadFile(OBJECT)和wx.getImageInfo(OBJECT)。都需先配置download域名才能生效。 三、裁切“圆形”头像画图 [代码]ctx.save(); //保存画图板 ctx.beginPath()//开始创建一个路径 ctx.arc(35, 25, 15, 0, 2 * Math.PI, false)//画一个圆形裁剪区域 ctx.clip()//裁剪 ctx.closePath(); ctx.drawImage(headImageLocal, 20, 10, 30, 30); ctx.draw(true); ctx.restore()//恢复之前保存的绘图上下文 [代码] 使用图形上下文的不带参数的clip()方法来实现Canvas的图像裁剪功能。该方法使用路径来对Canvas话不设置一个裁剪区域。因此,必须先创建好路径。创建完整后,调用clip()方法来设置裁剪区域。 需要注意的是裁剪是对画布进行的,裁切后的画布不能恢复到原来的大小,也就是说画布是越切越小的,要想保证最后仍然能在canvas最初定义的大小下绘图需要注意save()和restore()。画布是先裁切完了再进行绘图。并不一定非要是图片,路径也可以放进去~ 小程序 canvas 裁切BUG [代码]ctx.setFillStyle("#fff"); ctx.fillRect(0, 0, 320, 500); //第一个填充矩形 wx.downloadFile({ url: headUri, success(res) { ctx.beginPath() ctx.arc(50, 50, 25, 0, 2 * Math.PI) ctx.clip() ctx.drawImage(res.tempFilePath, 25, 25); //第二个填充图片 ctx.draw() ctx.restore() ctx.setFillStyle("#fff"); ctx.fillRect(0, 0, 320, 500); ctx.draw(true) ctx.restore() } }) [代码] clip裁切这个功能,如果有超过一张图片/背景叠加,则裁切效果失效。 错误参考:http://html51.com/info-38753-1/ 四、将canvas导出成虚拟地址 [代码]wx.canvasToTempFilePath({ fileType: 'jpg', canvasId: 'customCanvas', success: (res) => { console.log(res.tempFilePath) //为canvas的虚拟地址 } }) res: { errMsg: "canvasToTempFilePath:ok", tempFilePath: "http://tmp/wx02935bb29080a7b4.o6zAJswFAuZuKQ5NZfPr….cGnD1a02PlVC0b3284be3a41d08986c2477579a5fd8e.jpg" } [代码] 这里需要把canvas里面的内容,导出成一个临时地址才能保存在相册,比如: http://tmp/wx02935bb29080a7b4.o6zAJswFAuZuKQ5NZfPr5UfJVR4k.cGnD1a02PlVC0b3284be3a41d08986c2477579a5fd8e.jpg 五、询问并获取访问手机本地相册权限 [代码]wx.getSetting({ success(res) { console.log(res) if (!res.authSetting['scope.writePhotosAlbum']) { //判断权限 wx.authorize({ //获取权限 scope: 'scope.writePhotosAlbum', success() { console.log('授权成功') //转化路径 self.saveImg(); } }) } else { self.saveImg(); } } }) [代码] 判断是否有访问相册的权限,如果没有,则请求权限。 六、保存到用户手机本地相册 [代码]wx.saveImageToPhotosAlbum({ filePath: res.tempFilePath, success: function (data) { wx.showToast({ title: '保存到系统相册成功', icon: 'success', duration: 2000 }) }, fail: function (err) { console.log(err); if (err.errMsg === "saveImageToPhotosAlbum:fail auth deny") { console.log("当初用户拒绝,再次发起授权") wx.openSetting({ success(settingdata) { console.log(settingdata) if (settingdata.authSetting['scope.writePhotosAlbum']) { console.log('获取权限成功,给出再次点击图片保存到相册的提示。') } else { console.log('获取权限失败,给出不给权限就无法正常使用的提示') } } }) } else { wx.showToast({ title: '保存失败', icon: 'none' }); } }, complete(res) { console.log(res); } }) [代码] 保存到本地需要一定的时间,需要加一个loading的状态。 七、关于组件中引用canvas [代码]let ctx = wx.createCanvasContext('posterCanvas',this); //需要加this [代码] 在components中canvas无法选中的问题: 在components自定义组件下,当前组件实例的this,表示在这个自定义组件下查找拥有 canvas-id 的 <canvas> ,如果省略则不在任何自定义组件内查找。
2021-09-13