创建 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"></symbol>
</svg>
symbol
元素接受诸如 viewBox
和 preserveAspectRatio
之类的属性,它们在使用元素定义的矩形视图中提供缩放比例。简单地说,我们定义了初始的 x 和 y 轴坐标值(0,0),最终定义了 SVG 画布的宽度和高度(100,100)。
接下来我们需要添加一个形状,为动画添加涟漪效果。插入一个圆形元素。
<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 的坐标位置; 在我们的例子中,为了使点击显得更自然,我们需要确保在接收到输入时,直接在用户手指下方触发点击。
该图的中间示例的属性是创建一个半径为 1px 的 2px x 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>
最后,我们将包含一个 div
,以简洁地隐藏 sprite 。这样可以防止在渲染时占用页面中的空间。
创建标记
让我们创建一个用来实现涟漪效果的标记。为了利用先前创建的 symbol
元素,我们需要一种方法来引用它,方法是使用按钮的 SVG 中的 use 元素来引用 symbol
的 ID 属性值。
<button>
Click for Ripple
<svg>
<use xlink:href="#ripply-scott"></use>
</svg>
</button>
最终的标记具有 CSS 和 JavaScript 的附加属性。以 “js-” 开头的属性值表示仅存在于 JavaScript 中的值,如果删除它们将阻碍交互,但不会影响样式。这有助于区分 CSS 选择器与 JavaScript 钩子,以避免在将来需要删除或更新时混淆彼此。
<button id="js-ripple-btn" class="button styl-material">
Click for Ripple
<svg class="ripple-obj" id="js-ripple">
<use width="100" height="100" xlink:href="#ripply-scott" class="js-ripple"></use>
</svg>
</button>
use
元素必须有一个宽度和高度定义,否则它将不可见。你也可以在 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;
}
使用 JavaScript 添加效果
我们将使用 GreenSock 的 TweenMax 库,因为它是使用 JavaScript 对对象进行动画处理的最佳库之一; 特别是当涉及 SVG 动画跨浏览器的兼容性问题时。
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.17.0/TweenMax.min.js"></script>
<script src="js/ripple.js"></script>
我们将使用所谓的模块模式,因为它有助于隐藏和保护全局命名空间。
var ripplyScott = (function() {}
return {
init: function() {}
};
})();
我们将抓取一些元素并将它们存储在变量中; 特别是 use
元素,它在 button
中包含 svg
。整个动画逻辑将驻留在 rippleAnimation
函数中。此函数将接受动画时间和事件信息的参数。
var ripplyScott = (function() {}
var circle = document.getElementById('js-ripple'),
ripple = document.querySelectorAll('.js-ripple');
function rippleAnimation(event, timing) {…}
})();
我们需要定义一些变量:
var ripplyScott = (function() {}
var circle = document.getElementById('js-ripple'),
ripple = document.querySelectorAll('.js-ripple');
function rippleAnimation(event, timing) {
var tl = new TimelineMax(); //创建动画序列的时间轴实例以及所有时间轴在TweenMax中实例化的方式。
x = event.offsetX, //事件的偏移量是一个只读属性,它将鼠标指针的偏移量报告给目标节点的填充边。事件偏移量从左到右计算
y = event.offsetY, //事件偏移量从上到下计算。(上面的 x 和当前的 y 都是从 0 开始计算)
w = event.target.offsetWidth, //返回按钮的宽度。最终计算将包括元素边框和填充的大小。我们需要这个值来知道我们的元素有多大,因此我们可以将涟漪传播到最远的边缘。
h = event.target.offsetHeight, //返回按钮的高度。
offsetX = Math.abs( (w / 2) - x ), //通过将宽度除以一半找到中心,然后减去由我们的x坐标检测到的报告值。(见图2)
offsetY = Math.abs( (h / 2) - y ), //通过将高度除以一半找到中心,然后减去由我们的y坐标检测到的报告值。
deltaX = (w / 2) + offsetX, //Delta计算我们点击的整个距离,而不是距离中心的距离。从左到右检测点击。(见图3)
deltaY = (h / 2) + offsetY, //从右到左
scale_ratio = Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2)); //Math.pow()返回第一个参数的平方;将这两个值想加结果的平方根这是我们需要的涟漪比例。
}
})();
图 2
图 3
在 TweenMax 中使用 fromTo
方法,传递目标涟漪形状,并设置包含整个运动序列方向的对象文字。假如我们想要在中心形成动画,SVG 需要将变形原点设置为中间位置。由于我们希望在此之后进行动画处理,因此,缩放也需要调整到最小的位置,设置不透明度为1。因为我们在 CSS 中设置了不透明度为0的 use
元素,所以我们要从 1 返回到 0。 最后一部分是返回我们的时间轴实例。
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;
}
})();
返回的这个对象将通过将事件侦听器附加到所需的目标来控制涟漪,并调用 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));
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);
});
}
};
})();
最后,调用按钮!
ripplyScott.init('js-ripple-btn', 0.75);