今天,我们将创建一个可以通过点击,点击或使用键盘来演奏的SVG动画鼓套件,也可以编程为单独播放!我们将使用GreenSock的 TweenMax动画库,jQuery和<audio>元素。
打开你选择的矢量编辑软件(我使用的是Adobe Illustrator,但有许多选择,如Affinity Designer, Sketch或Inkscape),并得到绘图!我发现有几张照片想要画在我的画板上,以供参考。保持风格平坦,相当简单,无论是在绘画还是动画方面,都会让生活变得更加轻松,而功能较弱的设备会感谢您减少渲染的内容。在这种风格中,鼓套件实际上是一组相当简单的(大部分)矩形对象。在绘制任何想要动画的SVG的同时,还有几件事值得记住:
“g元素是一个用于分组其他SVG元素的容器。应用于g元素的转换将在其所有子元素上执行,并且其任何属性都由其子元素继承。
对于我们来说,这个关键部分是“在所有子元素上执行应用于g元素的转换”,这在将SVG动画化时非常方便, 我们可以简单地将我们想要的动画元素进行分组,并将单个动画应用到该组,从而不用将动画应用到小鼓的每个单独组件。 命名良好的组织将在以后更轻松地操纵SVG!
如果您的绘图应用程序有这样的选项,请启用XML标识名称(在Illustrator首选项>单位>标识对象:> XML ID),这允许您编辑/查看id我们将使用的元素和组的实际属性瞄准他们。
我们需要将两个主要元素集合在一起:
我们希望能够触发我们的动画和声音基于他们各自的鼓被点击/点击,所以我们需要将点击/点击“目标”元素分组在一起。 例如,将整个小鼓,包括支架组合在一起,所以用户触摸的任何部分都会触发动画/声音。
将我们想要动画的元素组合在一起。例如,将小军鼓组合起来; 当小军鼓动画被触发时,我们只想要鼓动画,而不是立场。
“我发现绘制所有东西都很有用,然后在需要时显示部分。”
我们希望能够用键盘演奏鼓,所以某些指示哪些键触发鼓将是有用的。选择你想要“演奏”每个鼓的键,并添加一些指示到您的SVG。将它们组合在一起,以便我们稍后可以隐藏/显示。
将所有组保存为一个.svg文件,然后在文本编辑器中打开它。
我们可以把它直接放到我们的HTML文档中。不过,我建议首先优化Web的SVG。有足够的工具可以做到这一点,SVGOMG可能是最简单的使用之一。
我们提供音频元素ID,以便我们可以在需要播放时对其进行定位。
<audio id="Snare-Audio"> <source src="mp3/Snare.mp3" type="audio/mp3"> </audio>
通过添加属性,preload="auto"我们可以告诉浏览器“用户需要这个,确保它被下载”。这就是它在某些浏览器中的工作原理。其他浏览器(特别是移动设备上的浏览器)实际上忽略了这一点,这可能会导致一些奇怪的行为,但我们稍后会回到这一点。
<audio id="Snare-Audio" preload="auto"> <source src="mp3/Snare.mp3" type="audio/mp3"> </audio>
在我们打鼓之前,我们还有很多事情要做,但每个鼓部分可以分成三个步骤:
音频、动画、触发这些用户交互。
首先将我们需要的DOM元素作为变量进行存储。例如,对于小鼓,我们想要小鼓音频和小鼓,两个位我们想要动画和部分将触发动画和音频。
播放我们的音频实际上非常简单:我们只是.play()在我们想播放的音频元素上使用该方法。
var snareAudio = $('#Snare-Audio'); snareAudio.get(0).play();
就是这样!嗯, 几乎..。这很有用, 但是如果我们在音频已经播放时再次调用该方法, 则不会发生任何事情。我们希望在每次触发鼓时从音频文件的开头触发声音。因此, 我们需要设置的音频开始位置是每次触发。
var snareAudio = $('#Snare-Audio'); var snareAudioEl = snareAudio.get(0); snareAudioEl.currentTime = 0; snareAudioEl.play();
对于那些不熟悉的人来说,TweenMax是一个JavaScript库,可以随时间处理任何对象(或对象数组)的一个或多个属性的“补间”。我不打算深入每一个动画的解剖,但每个可以分解成几个简单的步骤:
创建一个新的时间轴Tween属性补 (或大量属性)
我们只是试图模拟一些接近于我们“期望”鼓被击中时移动的东西。有时为了达到预期的效果,夸大运动量是值得的。这在一定程度上是反复试验的,但幸运的是,GreenSock在行动中有很多很好的例子,你可以检查灵感。同样,像Chris Gannon这样的人是看GreenSock能做什么的好方法。
最后,easing是GreenSock提供给你的动画最有用的工具之一。GreenSock有一个很容易的可视化,它可以帮助你决定什么是适合你的动画。
snareDrum = $('#Snare-Drum'); // Create a new timeline, that's paused by default var snaretl = new TimelineMax({ paused: true }); // The animation tweens snaretl.to(snareDrum, 0.1, {scaleX: 1.04, transformOrigin: "50% 50%", ease: Expo.easeOut}) .to(snareDrum, 0.1, {scaleY: 0.9, transformOrigin: "50% 100%", ease: Expo.easeOut}, '0') // The last tween, returns the element to it's original properties .to(snareDrum, 0.4, {scale: 1, transformOrigin: "50% 100%", ease: Elastic.easeOut});
我们有我们的音频和我们的动画准备。现在我们只需要把它放在一起并在用户交互中触发它。我们将音频和动画方法封装在一个函数中:
function snare(){ snaretl.restart(); snaretl.play(); var snareAudioEl = snareAudio.get(0); snareAudioEl.currentTime = 0; snareAudioEl.play(); }
然后,在点击/触摸事件上调用该函数:
var clickTouchSnareDone = false; snareDrumAll.on("touchstart click", function() { if(!clickTouchSnareDone) { clickTouchSnareDone = true; setTimeout(function() { clickTouchSnareDone = false; }, 100); snare(); return false; } });
当用户点击或触摸鼓时,会触发动画和音频。下一步是连接键盘。
document.onkeydown = function(e) { switch (e.keyCode) { case 72: snare(); break; } };
使用键盘时,我们甚至可以为键盘指定动画:
function animateKey(key) { keytl = new TimelineMax({ paused: true }); keytl.to(key, 0.1, {scale: 1.1, transformOrigin: "50% 50%", ease: Expo.easeOut}) .to(key, 0.4, {scale: 1, transformOrigin: "50% 50%", ease: Elastic.easeOut}); keytl.restart(); keytl.play(); } document.onkeydown = function(e) { // This string will have to match the id of your key guides thisKeyID = 'Key-' + e.keyCode; thisKey = $('#' + thisKeyID); switch (e.keyCode) { case 72: snare(); animateKey(thisKey); break; } };
冲洗并重复这个过程中的每个鼓在我们的套件。
现在我们已经有了一个功能齐全的鼓组套件,我们可以用鼠标,键盘或触摸来玩。但是,我们不要停留在这里。
我们有可玩的鼓,但可以使我们的鼓可编程。我们要构建一个可以编程来播放循环的音序器。这可能相当复杂,但同样可以分解成简单的步骤:
创建一个表示鼓和时间 (或节拍) 的 "矩阵"
循环通过列或节拍
如果鼓是活跃的在当前敲打, 演奏鼓
幸运的是,我可以确切地告诉你我们的矩阵是什么!这是一个简单的网格,其中:
行将代表我们的鼓列将代表节拍
我们需要能够选择在我们的音序器的每个节拍中是否播放鼓。<input>具有该type="checkbox"属性的< 元素是完全适合这个,但是,这不是那么容易的风格,但有一个黑客。
我们将为每个鼓创建一个行,并为每个节拍创建一个列。在这个演示中,我们将使用8节拍。我们将添加一个data-target-drum属性(这可以被称为任何东西)与我们之前创建的鼓功能的名称。我们可以使用该属性来帮助我们确定要调用哪个相应的函数。
<!-- A "row" of our sequencer, we'll need one of these for each drum --> <div class="row" data-target-drum="snare"> <!-- This image indicates which drum the row controls --> <img src="img/snare.png"> <label><input type="checkbox"><div></div></label> <label><input type="checkbox"><div></div></label> <label><input type="checkbox"><div></div></label> <label><input type="checkbox"><div></div></label> <label><input type="checkbox"><div></div></label> <label><input type="checkbox"><div></div></label> <label><input type="checkbox"><div></div></label> <label><input type="checkbox"><div></div></label> </div>
而且一次循环“行”和“拍”的功能如下所示:
rows = $('.row'); rowLength = rows.first().children().length; labels = $('label'); // Beat starts at 1 because 0 is the img for each row beat = 1; // Sequencer function sequencer () { labels.removeClass('active'); // Do this function for each .row $(rows).each(function() { // Select the child element at the "beat" index current = $(this).children().eq(beat); current.addClass('active'); // If the current input is checked do some stuff! if (current.find('input').is(":checked")) { targetDrum = (current.parent().attr('data-target-drum')); // If there a function that shares the same name as the data attribute, do it! fn = window[targetDrum]; if (typeof fn === "function") { fn(); } } }); // If we get to the last child, start over if ( beat < (rowLength - 1) ) { ++beat; } else { beat = 1; } }
时机就是一切
我们有一个功能来演奏当前节拍的鼓声。但我们需要循环这些节拍。我们可以用这个.setInterval()方法来做到这一点。它基本上定义了函数应该执行的频率(以毫秒为单位)。所以我们可以用它来启动音序器并设置其速度。在音乐方面,节拍(或节奏)的频率通常被称为bpm或每分钟节拍。所以bpm可以简单地定义为一分钟内的毫秒数除以bpm。我们将bpm设置为合理的默认值150.然后我们设置点击播放按钮的时间间隔。
sequencerOn = false; // Start Sequencer $('#sequencer-active-btn').click(function () { intervalId = window.setInterval(sequencer, interval); sequencerOn = true; });
这很好,但是当我们需要停止音序器时呢?我们可以通过将.setInterval()方法返回的ID传递给方法来停止顺序器.clearInterval():
sequencerOn = false; // Start/Stop Sequencer $('#sequencer-active-btn').click(function () { if (sequencerOn === false) { intervalId = window.setInterval(sequencer, interval); sequencerOn = true; } else { window.clearInterval(intervalId); sequencerOn = false; } });
如果我们可以改变我们的拍子的bpm以获得更快或更慢的循环,那将会更加酷:
bpm = 150; interval = 60000 / bpm; // Set tempo function setTempo() { window.clearInterval(intervalId); intervalId = window.setInterval(sequencer, interval); } // Increase tempo $('#bpm-increase-btn').click(function() { if ( bpm < 300 ) { bpm = parseInt($('#bpm-indicator').val()); bpm += 10; interval = 60000 / bpm; $('#bpm-indicator').val(bpm); if(sequencerOn === true) { setTempo(); } } }); //Decrease tempo $('#bpm-decrease-btn').click(function() { if ( bpm > 100 ) { bpm = parseInt($('#bpm-indicator').val()); bpm -= 10; interval = 60000 / bpm; $('#bpm-indicator').val(bpm); if(sequencerOn === true) { setTempo(); } } });
许多移动浏览器不仅忽略,preload="auto"而且实际上只会在用户触摸事件中加载音频。这基本上意味着我们的音序器不会触发任何音频,直到它已经被用户触发。所以我们需要在第一次用户交互中加载音频。这是一个黑客,但它意味着音序器将在移动设备上工作。
// Load audio on iOS devices on the first user interaction $('#sequencer-visible-btn').one('click', function() { $("audio").each(function(i) { this.play(); this.pause(); }); });
就是这样!我们制作了一个我们可以玩的鼓乐器,我们可以编程自己玩。我希望这个教程能够激发你创建自己的交互式动画SVG。使用相同的技巧,你可以建立其他乐器,或任何东西!如果你这样做,请让我知道,我很想看看你创造了什么!