SVG 創建 Material Design 波紋效果按鈕
隨著Google Material Design的出現,一種旨在跨平臺和設備創建統一體驗的視覺語言由此橫空出世。Google通過“Material Guidelines”動畫部分描述的例子是如此地擬真,以致于許多人將這些互動視為Google品牌的一部分。
在本教程中,我們將向大家展示如何在Google Material Design規范的 Radial Action 下構建波紋效果,并結合SVG和GreenSock功能。
響應式動作
Google使用Radial Action定義Responsive Interaction如下:
Radial action is the visual ripple of ink spreading outward from the point of input.
The connection between an input event and on-screen action should be visually represented to tie them together. For touch or mouse, this occurs at the point of contact. A touch ripple indicates where and when a touch occurs and acknowledges that the touch input was received.
Transitions, or actions triggered by input events, should visually connect to input events. Ripple reactions near the epicenter occur sooner than reactions further away.
Google非常清楚地表述了輸入反饋應從原點出發,向外擴散。例如,如果用戶直接在中心點擊按鈕,則紋波將從初始接觸點向外擴展。這就是我們如何指出觸摸發生的地點和時間的方式,以便向用戶確認接收到的輸入。
SVG中的徑向動作
有許多開發人員創作紋波技術,主要使用CSS技術,如@keyframes,transitions,transforms偽技巧,border-radius以及甚至額外的標記,如span或div。不使用CSS,讓我們來看看如何通過GreenSock的TweenMax庫用SVG來創建這個徑向動作。
創建SVG
不管你信不信,其實我們并不需要如Adobe Illustrator或甚至Sketch這樣花哨的應用程序來創作這個效果。SVG的標記可以使用我們可能已經熟悉并用到工作中的幾個XML標簽來編寫。
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<symbol viewbox="0 0 100 100"/>
</svg>
對于使用SVG精靈圖標的用戶,你會注意到的使用。symbol元素允許在單個symbol實例中匹配相關的XML,并隨后實例化它們,或者換句話說——就像蓋章一樣在整個應用程序中使用它們。每個蓋章的實例與其唯一的創建者相同:它所在的symbol。
symbol元素接受諸如viewBox和preserveAspectRatio之類的屬性,這些屬性可以在引用use元素定義的矩形視口中提供符合縮放比例的能力。Sara Soueidan寫了一篇精彩的文章,并建立了一個交互式工具,以幫助你了解viewBox坐標系統。簡單地說就是,定義初始的x和y坐標值(0,0),然后定義SVG畫布的寬度和高度(100,100)。
這個XML拼圖的下一個部分是添加我們打算動畫化為波紋的形狀。這是放入circle元素的地方。
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<symbol viewbox="0 0 100 100">
<circle/>
</symbol>
</svg>
circle需要一些更多的信息,然后它才能在SVG的viewBox內正確地顯示。
<circle cx="1" cy="1" r="1"/>
屬性cx和cy是相對于SVG viewBox的坐標位置;我們的例子中就是symbol。為了使點擊的時候感覺更自然,我們需要確保在接收到輸入時觸發點直接放在用戶手指下方。
上圖中間那個例子,其屬性創建了一個半徑為1px大小為2px × 2px的圓。這將確保我們的圓不會像最后那個示例中所看到的那樣裁剪。
<div style="height: 0; width: 0; position: absolute; visibility: hidden;" aria-hidden="true">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" focusable="false"><symbol id="ripply-scott" viewbox="0 0 100 100"><circle id="ripple-shape" cx="1" cy="1" r="1"/></symbol></svg></div>
對于最后的觸摸,我們將用包含內聯CSS的div來包裝它,以簡潔地隱藏sprite。這樣可以防止在渲染時占用頁面中的空間。
在撰寫本文時,SVG精靈包含symbol塊引用它自己的漸變定義——正如你在演示中將看到的——通過ID找不到漸變和正確地渲染;使用visibility 屬性代替display的原因:none在Firefox和其他大多數瀏覽器上作為整個漸變都會失敗。
所有IE直到IE11都需要使用focusable=”false” ;除了Edge,因為它還沒有測試過。這是來自SVG 1.2規范的一個提案,描述了鍵盤焦點控制應該如何工作。IE實現了這一點,其他的瀏覽器則不行。為了與HTML一致,并且為了更好的控制,SVG 2將轉而采用tabindex。
編寫標記
讓我們寫一個語義的button元素作為我們的對象,以顯示此波紋。
<span class="hljs-tag"><<span class="hljs-name">button</span>></span>Click for Ripple<span class="hljs-tag"></<span class="hljs-name">button</span>></span>
大多數我們熟悉的button的標記結構是直截了當的,包括一些填充文本。
<span class="hljs-tag"><<span class="hljs-name">button</span>></span>
Click for Ripple
<span class="hljs-tag"><<span class="hljs-name">svg</span>></span>
<span class="hljs-tag"><<span class="hljs-name">use</span> <span class="hljs-attr">xlink:href</span>=<span class="hljs-string">"#ripply-scott"</span>></span><span class="hljs-tag"></<span class="hljs-name">use</span>></span>
<span class="hljs-tag"></<span class="hljs-name">svg</span>></span>
<span class="hljs-tag"></<span class="hljs-name">button</span>></span>
為了利用先前創建的symbol元素,我們需要方法來引用它,通過使用按鈕的SVG中的use元素來引用符號的ID屬性值。
<button id=<span class="hljs-string">"js-ripple-btn"</span> <span class="hljs-keyword">class</span>=<span class="hljs-string">"button styl-material"</span>>
Click <span class="hljs-keyword">for</span> Ripple
<svg <span class="hljs-keyword">class</span>=<span class="hljs-string">"ripple-obj"</span> id=<span class="hljs-string">"js-ripple"</span>>
<use width=<span class="hljs-string">"100"</span> height=<span class="hljs-string">"100"</span> xlink:href=<span class="hljs-string">"#ripply-scott"</span> <span class="hljs-keyword">class</span>=<span class="hljs-string">"js-ripple"</span>></use>
</svg>
</button>
最終標記具備了CSS和JavaScript hooks的附加屬性。以“js-”開頭的屬性值表示僅存在于JavaScript中的值,因此刪除它們將阻礙交互,但不會影響樣式。這有助于區分CSS選擇器和JavaScript hooks,以避免在將來需要刪除或更新時相互混淆。
use元素必須有定義的寬度和高度,否則將不會對查看者可見。你也可以在CSS中定義,如果你直接在元素本身上決定不要的話。
聯結點樣式
當編寫CSS的時候,要達到預期的效果你所要做的并不多。
.ripple-obj {
height: 100%;
pointer-events: none;
position: absolute;
top: 0;
left: 0;
width: 100%;
z-index: 0;
fill: #0c7cd5;
}
.ripple-obj use {
opacity: 0;
}
這就是在刪除用于一般樣式的聲明時,還留下的內容。pointer-events的使用消除了SVG紋波成為鼠標事件的目標,因為我們只需要父對象反應:button元素。
紋波最初必須是不可見的,因此要將不透明度值設置為零。我們還將波紋對象定位在button的左上方。我們可以使波紋形狀居中,但是由于此事件是基于用戶交互而發生的,所以擔心位置沒有意義。
賦予它生機
賦予生機正是這個互動所有的意義。
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.17.0/TweenMax.min.js"/>
<script src="js/ripple.js"/>
為了動畫化波紋,我們將使用GreenSock的TweenMax庫,因為它是使用JavaScript對對象進行動畫處理的最佳庫之一;特別是涉及與動畫SVG跨瀏覽器有關的問題。
var ripplyScott = (function() {}
return {
init: function() {}
};
})();
我們將要使用的模式是所謂的模塊模式,因為它有助于隱藏和保護全局命名空間。
var ripplyScott = (function() {}
var circle = document.getElementById('js-ripple'),
ripple = document.querySelectorAll('.js-ripple');
function rippleAnimation(event, timing) {…}
})();
為了解決問題,我們將抓取一些元素并將它們存儲在變量中;特別是use元素,它包含button內的svg。整個動畫邏輯將駐留在rippleAnimation函數中。該函數將接受動畫序列和事件信息的時序參數。
var ripplyScott = (function() {}
var circle = document.getElementById('js-ripple'),
ripple = document.querySelectorAll('.js-ripple');
function rippleAnimation(event, timing) {
var tl = new TimelineMax();
x = event.offsetX,
y = event.offsetY,
w = event.target.offsetWidth,
h = event.target.offsetHeight,
offsetX = Math.abs( (w / 2) - x ),
offsetY = Math.abs( (h / 2) - y ),
deltaX = (w / 2) + offsetX,
deltaY = (h / 2) + offsetY,
scale_ratio = Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
}
})();
我們定義了大量的變量,所以讓我們一個一個地討論這些變量所負責的內容。
var tl = new TimelineMax();
此變量創建動畫序列的時間軸實例以及所有時間軸在TweenMax中實例化的方式。
var x = event.offsetX;
var y = event.offsetY;
事件偏移量是一個只讀屬性,它將鼠標指針的偏移值報告給目標節點的填充邊。在這個例子中,就是我們的button。x的事件偏移量從左到右計算,y的事件偏移量從上到下計算;都從零開始。
var w = event.target.offsetWidth;
var h = event.target.offsetHeight;
這些變量將返回按鈕的寬度和高度。最終計算結果將包括元素邊框和填充的大小。我們需要這個值才能知道我們的元素有多大,這樣我們才可以將波紋傳播到最遠的邊緣。
var offsetX = Math.abs( (w / 2) - x );
var offsetY = Math.abs( (h / 2) - y );
偏移值是點擊距離元素中心的偏移距離。為了填滿目標的整個區域,波紋必須足夠大,可以從接觸點覆蓋到最遠的角落。使用初始x和y坐標將不會再次將其從零開始,對于x,是從左到右的值,對于y,是從上到下的值。這種方法讓我們使用這些值的時候無論目標的中心點點擊在哪一邊,都會檢測距離。
注意圓將如何覆蓋整個元素的過程,無論輸入的起始點何處發生。根據起始點的交互來覆蓋整個表面,我們需要做一些數學。
以下是我們如何使用464 x 82作為寬和高,391和45作為x和y坐標來計算偏移量的過程:
var offsetX = (464 / 2) - 391 = -159
var offsetY = (82 / 2) - 45 = -4
通過將寬度和高度除以2來找到中心,然后減去由x和y坐標檢測到的報告值。
Math.abs()方法返回數字的絕對值。使用上面的算術得到值159和4。
var deltaX = 232 + 159 = 391;
var deltaY = 41 + 4 = 45;
三角計算點擊的整個距離,而不是距離中心的距離。選擇三角的原因是x和y總是從零開始從左到右,所以當相反方向(從右到左)點擊的時候,我們需要方法來檢測點擊。
學過基礎數學課程的小伙伴應該都知道勾股定理。公式為:高(a)的平方加底(b)的平方,得到斜邊(c)的平方。
a 2 + b 2 = c 2
var scale_ratio = Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
使用這個公式讓我們來看一下計算:
var scale_ratio = Math.sqrt(Math.pow(391, 2) + Math.pow(45, 2));
Math.pow()方法返回第一個參數的冪;在這個例子中增加了一倍。391的2次方為152881。后面45的2次方等于2025。將這兩個值相加并取結果的平方根將留下393.58099547615353,這就是我們需要的波紋比例。
var ripplyScott = (function() {
var circle = document.getElementById('js-ripple'),
ripple = document.querySelectorAll('.js-ripple');
function rippleAnimation(event, timing) {
var tl = new TimelineMax();
x = event.offsetX,
y = event.offsetY,
w = event.target.offsetWidth,
h = event.target.offsetHeight,
offsetX = Math.abs( (w / 2) - x ),
offsetY = Math.abs( (h / 2) - y ),
deltaX = (w / 2) + offsetX,
deltaY = (h / 2) + offsetY,
scale_ratio = Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
tl.fromTo(ripple, timing, {
x: x,
y: y,
transformOrigin: '50% 50%',
scale: 0,
opacity: 1,
ease: Linear.easeIn
},{
scale: scale_ratio,
opacity: 0
});
return tl;
}
})();
使用TweenMax中的fromTo方法,我們可以傳遞目標——波紋形狀——并設置包含整個運動序列方向的對象文字。鑒于我們想要從中心向外形成動畫,SVG需要將轉換原點設置為中間位置。考慮到我們想要之后要進行動畫處理,需要設置opacity 為1,因此縮放也需要調整到最小的位置。不知道你回想起了沒有,之前我們在CSS中設置了opacity為0的use元素以及我們從值1開始并返回到零的原因。最后部分是返回時間軸實例。
var ripplyScott = (function() {
var circle = document.getElementById('js-ripple'),
ripple = document.querySelectorAll('.js-ripple');
function rippleAnimation(event, timing) {
var tl = new TimelineMax();
x = event.offsetX,
y = event.offsetY,
w = event.target.offsetWidth,
h = event.target.offsetHeight,
offsetX = Math.abs( (w / 2) - x ),
offsetY = Math.abs( (h / 2) - y ),
deltaX = (w / 2) + offsetX,
deltaY = (h / 2) + offsetY,
scale_ratio = Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
tl.fromTo(ripple, timing, {
x: x,
y: y,
transformOrigin: '50% 50%',
scale: 0,
opacity: 1,
ease: Linear.easeIn
},{
scale: scale_ratio,
opacity: 0
});
return tl;
}
return {
init: function(target, timing) {
var button = document.getElementById(target);
button.addEventListener('click', function(event) {
rippleAnimation.call(this, event, timing);
});
}
};
})();
返回的對象字面值將控制我們的波紋,方法是通過將事件偵聽器附加到所需的目標,調用rippleAnimation,以及最后傳遞我們將在下一步討論的參數。
ripplyScott.init('js-ripple-btn', 0.75);
最后通過使用模塊并傳遞init函數來對按鈕進行調用,init函數傳遞按鈕和序列的時序。看,就是這樣!
希望你喜歡這篇文章,并從中受到啟迪!歡迎使用不同的形狀來檢查演示,并查看源代碼。不妨嘗試新的形狀、新的圖層形狀,最重要的是發揮你的想象力,放飛你的創意。
注意:其中一些技術是試驗性的,只能在現代瀏覽器中運行。
瀏覽器支持:Chrome Firefox Internet Explorer Safari Opera
來自:http://web.jobbole.com/92742/