MorphSVGPlugin from GreenSock 的源码注释分析
2015-10-11 19:19
274 查看
不管是ActionScript(flash)还是JavaScript,GreenSock的GSAP几乎是公认的最强动画库。
常用的就是其中的TweenLite、TweenMax、TimelineLite、TimelineMax、CSSPlugin等。
几天前GreenSock发布了MorphSVGPlugin,是收费的,官方的demo真的很炫,效果如下:
可以在这里看到效果和修改代码:http://codepen.io/GreenSock/pen/WQjRXE
好奇心促使我去研究了下官方演示效果是如果实现的,边分析边在源码旁写下了注释,下面就来分享下我的分析。
首先index页面是这样的(svg的路径太多,用省略号表示):
然后它的JavaScript是这样的(index.js):
最难的就是waveSVG函数了,就是让披肩飘动起来的函数,涉及的数学太多了,目前有些还看不懂,但大致就是将传入的svg里面的元素不断变换路径,让它看起来像是飘起来一样,如果有知道waveSVG函数更加细节的还请不吝赐教哈
常用的就是其中的TweenLite、TweenMax、TimelineLite、TimelineMax、CSSPlugin等。
几天前GreenSock发布了MorphSVGPlugin,是收费的,官方的demo真的很炫,效果如下:
可以在这里看到效果和修改代码:http://codepen.io/GreenSock/pen/WQjRXE
好奇心促使我去研究了下官方演示效果是如果实现的,边分析边在源码旁写下了注释,下面就来分享下我的分析。
首先index页面是这样的(svg的路径太多,用省略号表示):
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>MorphSVGPlugin from GreenSock</title> <link rel="stylesheet" href="css/style.css"> </head> <body> <svg width="90%" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 804 340" style="enable-background:new 0 0 804 340;" xml:space="preserve"> <!--样式--> <style type="text/css"> ... </style> <!--背景--> <rect id="background" width="804" height="340"/> <!--披肩飘动流1--> <g id="capeFlow1"> ... </g> <!--披肩飘动流2--> <g id="capeFlow2"> ... </g> <!--GreenSock的logo--> <g id="GUY"> ... </g> <!--披肩(字母变体后的灰色形状)--> <path id="cape" class="st8 hide" d="..."/> <!--腿(字母变体后的灰色形状)--> <path id="legs" class="st9 hide" d="..."/> <!--袜子(字母变体后的绿色形状)--> <path id="sock" class="st0 hide" d="..."/> <!--头部(字母变体后的灰色形状)--> <path id="head" class="st9 hide" d="..."/> <!--身体躯干(字母变体后的灰色形状)--> <path id="torso" class="st10 hide" d="..."/> <!--开场用的Morph单词--> <g id="Morph"> ... </g> <!--字母SVG以及它们对应的圆--> <g id="svg"> <span style="white-space:pre"> </span>... </g> <!--各种形状--> <g id="Shapes"> <!--心--> <path id="heart" class="shape" d="..."/> <!--星星--> <path id="star" class="shape" d="..."/> <!--灯泡--> <path id="light" class="shape" d="..."/> <!--拇指--> <path id="thumb" class="shape" d="..."/> <!--插头--> <path id="plug" class="shape" d="..."/> <!--苹果--> <path id="apple" class="shape" d="..."/> <!--钟--> <path id="clock" class="shape" d="..."/> <!--火箭--> <path id="rocket" class="shape" d="..."/> </g> <!--MorphSVGPlugin单词--> <g id="plugin"> ... </g> <!--fromGreenSock的单词--> <g id="fromGreenSock"> ... </g> <!--灯泡--> <g id="bulb"> ... </g> <g id="controls"> <line id="track" x1="62" y1="322" x2="744" y2="322"/> <circle id="scrubber" cx="62" cy="322" r="9"/> </g> </svg> <script src='js/TweenMax.min.js'></script> <script src='js/MorphSVGPlugin.min.js'></script> <script src='js/Draggable.min.js'></script> <script src="js/index.js"></script> </body> </html>
然后它的JavaScript是这样的(index.js):
TweenLite.set("svg", {position:"absolute", left:"50%", xPercent:-50});//水平居中 TweenLite.defaultEase = Power4.easeInOut;//默认缓动函数 var tl = new TimelineMax({delay:0.3}),//整体时间轴 flap = new TimelineMax({repeat:10}), //披肩飘动动画时间轴 scrubTween = TweenLite.to("#scrubber", 10, {x:682, ease:Linear.easeNone, paused:true});//进度条动画 /** * 第一步开场动画,Morph单词变体为greensock的logo */ tl.to("#M", 2, {morphSVG:{shape:"#cape", shapeIndex:-19}, fill:"#444"}, 0) //字母M变体成披肩 .to("#H", 2, {morphSVG:"#torso", fill:"#777"}, 0.1) //字母H变体成身体 .to("#P", 2, {morphSVG:{shape:"#legs", shapeIndex:-24}, fill:"#777"}, 0.1) //字母P变体成腿 .to("#O", 2, {morphSVG:"#head", fill:"#777"}, 0.05) //字母O变体成头 .to("#R", 1.5, {morphSVG:{shape:"#sock", shapeIndex:-11}, fill:"#88CE02", transformOrigin:"-100 60"}, 0.5) //字母R变体成袜子 .set("#GUY, #capeFlow1", {visibility:"visible", opacity:1}, 1.6) //将具体logo显示出来 .to("#M, #O, #R, #P, #H", 0.7, {autoAlpha:0, ease:Linear.easeNone}, 1.6); //渐渐隐藏所有字母,让greensock的logo显示出来 flap.add([ waveSVG( document.getElementById("capeBottom1"), { taperEnd: 80, taperStart:2, loose:true, length:120, angle:-52, magnitude:10,//量级 phase:110,//相位 duration:2,// start:6,//贝塞尔曲线开始的点 end:15,//贝塞尔曲线结束的点 repeat:10//重复次数 } ), waveSVG( document.getElementById("shadowBottom1"), {taperEnd: 80, taperStart:2, loose:true, length:120, angle:-32, magnitude:10, phase:110, duration:2, start:7, end:12, repeat:10} ), waveSVG( document.getElementById("shadowTop1"), {loose:true, length:120, angle:-16, magnitude:20, phase:-140, duration:2, start:10, end:14, repeat:10} ), waveSVG( document.getElementById("capeTop1"), {loose:true, angle: 70, length:120, magnitude:10, phase:20, duration:2, start:2, end:9, repeat:10} ) ]); /** * 余下的动画 */ tl.add(flap, 2) .addLabel("SVG", 2.3) .staggerFrom("#S, #V, #G", 0.8, {x:-50, autoAlpha:0, rotation:-90, ease:Back.easeOut, transformOrigin:"center center"}, 0.15, "SVG") //第二步动画,SVG三个字母出来 .addLabel("circles", "SVG+=2") //第三步动画,SVG字母变体成圆 .to("#S", 0.8, {morphSVG:{shape:"#circleOuter", shapeIndex:-1}, fill:"#444", ease:Power3.easeInOut}, "circles")//S变体成外圈圆 .to("#G", 0.8, {morphSVG:{shape:"#circleMid", shapeIndex:-8}, fill:"#777", ease:Power3.easeInOut}, "circles+=0.1")//G变体成中圈圆 .to("#V", 0.8, {morphSVG:{shape:"#circleInner", shapeIndex:10}, fill:"#000", ease:Power3.easeInOut}, "circles+=0.2")//V变体成内圈圆 //第四步动画,在圆内变体各种形状 .addLabel("shapes", 4.9) .from("#heart", 0.4, {scale:0.1, autoAlpha:0, ease:Back.easeOut, transformOrigin:"center center"}, "shapes")//心形出现 .to("#heart", 1, {morphSVG:{shape:"#star"}, fill:"#FFFF66"}, "shapes+=1")//心形变体成星形 .to("#heart", 1, {morphSVG:{shape:"#thumb"}, fill:"#3399CC"}, "shapes+=2.5")//变体成拇指形 .to("#heart", 1, {morphSVG:{shape:"#rocket"}, fill:"orange"}, "shapes+=4")//变体成火箭形 .to("#heart", 1, {morphSVG:{shape:"#apple"}, fill:"#CC0000"}, "shapes+=5.5")//变体成苹果形 .to("#heart", 1, {morphSVG:{shape:"#plug"}, fill:"#9966CC"}, "shapes+=7")//变体成插头 .to("#heart", 1, {morphSVG:{shape:"#light", shapeIndex:[-19, -13]}, fill:"white"}, "shapes+=8.5")//变体成灯泡 .staggerTo("#S, #V, #G", 0.8, {scale:0, autoAlpha:0, ease:Back.easeIn, transformOrigin:"center center"}, 0.1, "shapes+=10")//三个圆缩小到消失 .set("#heart", {autoAlpha:0}, "shapes+=10")//将灯泡(是心形经过一系列变体形成的)隐藏 .set(".bulb", {autoAlpha:1}, "shapes+=10")//将灯泡分解成许多细节的svg给显示出来替换之前的没有分解的灯泡 //将分解了的灯泡的全部14个组件依次变体成“MorphSVGPlugin”14个字母 .staggerTo(".bulb", 1.5, {cycle:{morphSVG:["#morph1", "#morph2", "#morph3", "#morph4", "#morph5", "#morph6", "#morph7", "#morph8", "#morph9", "#morph10", "#morph11", "#morph12", "#morph13", "#morph14"]}}, 0.05, "shapes+=10.3") //将fromGreenSock字母显示出来 .from("#fromGreenSock", 1, {x:50, autoAlpha:0, ease:Power2.easeOut}, "shapes+=12"); //利用Draggable工具控制进度条 Draggable.create("#scrubber", { type:"x", bounds:{maxX:682, minX:0}, onPress: function() { tl.pause(); }, onDrag:function() { tl.time(20 * this.x / 682); }, onRelease:function() { tl.resume(); } }); function updateScrubber() {//边播放,边更新进度条 scrubTween.progress(Math.min(1, tl.time() / 20)); } tl.time(20).restart(true); //强制最开始初始化,增强运行时性能 tl.eventCallback("onUpdate", updateScrubber);//监听播放进度 /** * 结合三角正弦函数和MorphSVGPlugin处理披肩飘动 * the function below is a bit advanced and it handles the flapping cape. * It leverages MorphSVGPlugin to do some heavy lifting, but ultimately relies * on some custom triganometry applied in an onUpdate to manipulate the points on a sine wave. */ function waveSVG(e, vars) { var _placeDot = function (x, y, vars) { var _createSVG = function(type, attributes) { var element = document.createElementNS("http://www.w3.org/2000/svg", type), reg = /([a-z])([A-Z])/g, p; for (p in attributes) { element.setAttributeNS(null, p.replace(reg, "$1-$2").toLowerCase(), attributes[p]); } return element; }, dot = _createSVG("circle", {cx:x, cy:y, r:vars.size || 6, fill:vars.color || "red"}), container = vars.container || document.querySelector("svg"); if (container) { container.appendChild(dot); } return dot; }, _getLength = function(x, y, x2, y2) {//得到两点间的距离 x = x2 - x; y = y2 - y; return Math.sqrt(x * x + y * y); }, _getTotalLength = function(bezier, start, end) {//得到该条贝塞尔曲线的总长度 var x = bezier[start], y = bezier[start+1], length = 0, i; for (i = start; i < end; i += 2) { length += _getLength(x, y, x=bezier[i], bezier[i+1]); } return length; }, _DEG2RAD = Math.PI / 180,//角度转弧度所需的乘数 _RAD2DEG = 180 / Math.PI,//弧度转角度所需的乘数 bezier = MorphSVGPlugin.pathDataToRawBezier(e.getAttribute("d"))[0],//将形状路径转换成贝塞尔曲线 start = (vars.start || 0) * 2,//bezier数组的开始角标 end = (vars.end === 0) ? 0 : (vars.end * 2) || (bezier.length - 1),//bezier数组的结束角标 length = vars.length || 100, magnitude = vars.magnitude || 50, proxy = {a:0}, debug = !!vars.debug,//是否为开发模式 phase = (vars.phase || 0) * _DEG2RAD,//相位 taperStart = vars.taperStart || 0, taperEnd = vars.taperEnd || 0, startX = bezier[start], startY = bezier[start + 1], changes = [], bezierLength = 0, loose = !!vars.loose,//if true, we'll just make the points influence the current positions instead of forcing them strictly onto the wave. tl = new TimelineMax({repeat:vars.repeat}), bezierTotalLength, angle, i, x, y, dx, dy, sin, cos, sin2, cos2, m, pathStart, t, negCos, negSin, rotatedStartX; if (end >= bezier.length-1) { end = bezier.length - 2; } if (start >= bezier.length) { start = bezier.length - 1; } bezierTotalLength = _getTotalLength(bezier, start, end);//得到该贝塞尔曲线片段的长度 dx = bezier[end] - startX;//起点与终点的x方向上的差值 dy = bezier[end+1] - startY;//起点与终点的y方向上的差值 if (vars.angle || vars.angle === 0) { angle = vars.angle * _DEG2RAD; } else { angle = Math.atan2(dy, dx) - Math.PI / 2; } sin = Math.sin(angle); cos = Math.cos(angle); sin2 = Math.sin(angle + Math.PI / 2); cos2 = Math.cos(angle + Math.PI / 2); negCos = Math.cos(-angle); negSin = Math.sin(-angle); rotatedStartX = startX * negCos + startY * negSin; console.log("negCos:"+negCos+",cos:"+cos); if (debug) { //note: if debug is true, we drop a red dot at the beginning, yellow at the end, blue dots as control points, and purple as anchors. _placeDot(bezier[start], bezier[start + 1], {container: e.parentNode, color:"red"}); _placeDot(bezier[end], bezier[end + 1], {container: e.parentNode, color:"yellow"}); console.log("waveSVG() angle: ", angle * _RAD2DEG, "degrees. RED dot is start, YELLOW is end."); } x = startX; y = startY; for (i = start; i < end; i += 2) { bezierLength += _getLength(x, y, x=bezier[i], y=bezier[i+1]); dx = x * negCos + y * negSin; //rotated in the opposite direction dy = x * negSin + y * negCos; t = (taperStart && bezierLength < taperStart) ? bezierLength / taperStart : (taperEnd && bezierLength > bezierTotalLength - taperEnd && bezierLength < bezierTotalLength) ? ((bezierTotalLength - bezierLength) / taperEnd) : 1; //taper m = Math.sin((dx / length) * Math.PI * 2 + phase) * magnitude; changes.push( {i: i - (start ? 2 : 0), p:dx, a: (dx / length) * Math.PI * 2 + phase, t:t, x: loose ? x - m * sin * t : startX + (dx - rotatedStartX) * cos2 * t, y: loose ? y - m * cos * t : startY + (dx - rotatedStartX) * sin2 * t, smooth: (i % 6 === 0 && i > start && i < end) ? Math.abs( Math.atan2(y - bezier[i-1], x - bezier[i-2]) - Math.atan2(bezier[i+3] - y, bezier[i+2] - x) ) < 0.01 : false} ); if (debug) { changes[changes.length-1].dot = _placeDot(x, y, {container: e.parenNode, size:3, color: (i % 6) ? "blue" : "purple"}); } } //when we're not animating the first point, optimize things by taking out the first x/y and treat them independently so we can merely bezier.join(",") on each update. if (start) { pathStart = "M" + bezier.shift() + "," + bezier.shift() + " C"; } tl.to(proxy, vars.duration || 3, {a:Math.PI * 2, ease:vars.ease || Linear.easeNone, onUpdate:function() {//将0逐渐变成2PI,监听变化, var l = changes.length, angle = proxy.a, node, i, m, x, y, x2, y2, x1, y1, cp, dx, dy, d, a, cpCos, cpSin; for (i = 0; i < l; i++) { node = changes[i]; if (node.smooth || i === l - 1 || !changes[i + 1].smooth) { m = Math.sin(node.a + angle) * magnitude * node.t; bezier[node.i] = x = node.x + m * sin; bezier[node.i + 1] = y = node.y + m * cos; if (node.smooth) { //make sure smooth anchors stay smooth! cp = changes[i - 1]; m = Math.sin(cp.a + angle) * magnitude * cp.t; x1 = cp.x + m * sin; y1 = cp.y + m * cos; cp = changes[i + 1]; m = Math.sin(cp.a + angle) * magnitude * cp.t; x2 = cp.x + m * sin; y2 = cp.y + m * cos; a = Math.atan2(y2 - y1, x2 - x1); cpCos = Math.cos(a); cpSin = Math.sin(a); dx = x2 - x; dy = y2 - y; d = Math.sqrt(dx * dx + dy * dy); bezier[cp.i] = x + cpCos * d; bezier[cp.i + 1] = y + cpSin * d; cp = changes[i - 1]; dx = x1 - x; dy = y1 - y; d = Math.sqrt(dx * dx + dy * dy); bezier[cp.i] = x - cpCos * d; bezier[cp.i + 1] = y - cpSin * d; i++; } } } if (debug) { for (i = 0; i < l; i++) { node = changes[i]; node.dot.setAttribute("cx", bezier[node.i]); node.dot.setAttribute("cy", bezier[node.i + 1]); } } else if (start) { //最核心的步骤,替换path的d属性,即路径,改变图形的形状,看起来像是飘动的感觉 e.setAttribute("d", pathStart + bezier.join(",")); } else { e.setAttribute("d", "M" + bezier[0] + "," + bezier[1] + " C" + bezier.slice(2).join(",")); } }}); return tl; }
最难的就是waveSVG函数了,就是让披肩飘动起来的函数,涉及的数学太多了,目前有些还看不懂,但大致就是将传入的svg里面的元素不断变换路径,让它看起来像是飘起来一样,如果有知道waveSVG函数更加细节的还请不吝赐教哈
相关文章推荐
- 字符串基础
- Oracle-增、删、改和事务
- sga和pga
- nasm中的enter
- struts2的OGNL表达式理解(一)
- startActivityForResult用法
- 第四周学习博客20135221黄卫
- Rhel7 设置目录权限,acl权限
- sql次级语句
- HDU 5500 Reorder the Books 思维分析题
- WordPress中wp-Syntax插件使用方法
- DP-HDOJ-5400-Arithmetic Sequence
- BestCoder Round #59 (div.2)A.SDOI
- block回调的一个小例子
- 生产者与消费者问题【java实现】
- UISrollView
- Java网络编程注意事项1
- Rhel7 配置yum
- 创建正则表达式(转载)
- 第六周项目1--建立顺序栈算法库