Целенаправленная анимация
Анимации в действии
Для того, чтобы увидеть все WinJS-анимации в действии, запустите пример "Библиотека анимации HTML" (http://code.msdn.microsoft.com/windowsapps/Using-the-Animation-787f3720). В нем показано очень много разных анимаций, и этот пример, без сомнения, получает награду за самое большое количество сценариев: двадцать два! На самом деле, первое, что вам следует сделать, это перейти в Сценарий 22 и посмотреть, включены ли анимации, так как это, при прочих равных условиях, сильно подействует на то, что вы можете увидеть. В этом сценарии вы увидите, установлен ли флаг UISettings.animationsEnabled, и здесь вы можете увеличить или уменьшить счетчик активности анимации в WinJS. Поэтому взгляните на него прямо сейчас, так как если вы похожи на меня (мне не нравится ждать, пока моя панель задач выдвигается и задвигается), вы могли выключить системные анимации для того, чтобы вам удобнее было работать с рабочим столом. Поначалу я не знал, что эта установка воздействует и на WinJS!
Очевидно, я не буду приводить здесь код каждого из 22 сценариев, на самом деле, делать это необязательно, так как многие из них работают сходным образом. Единственное реальное различие наблюдается между теми, методы которых начинаются с create, и теми, у которых это не так, что мы скоро увидим.
Все методы анимаций возвращают promise-объекты, которые вы можете использовать для того, чтобы предпринять дополнительные действия, когда анимация завершена (здесь будет вызван обработчик завершения). Если вы уже знаете немного о CSS-переходах и анимациях, вы можете верно догадываться, что эти promise-объекты включают в себя события наподобие transitionend и animationend, поэтому вам не нжужно непосредственно прослушивать эти события, если вы хотите объединить анимации в цепочку или синхронизировать. Для объединения в цепочку, вы можете просто объединить в цепочку promise-вызовы, для целей синхронизации вы можете получить promise-объекты для нескольких анимаций и подождать их завершения, используя методы наподобие WinJS.Promise.join или WinJS.Promise.any.
Promise-объекты анимаций, кроме того, поддерживают метод cancel, который убирает анимацию элемента. Этот метод немедленно устанавливает значение свойства, на которое воздействует, в его конечное состояние, что приводит к немедленному видимому переходу элемента в это состояние. И отменили ли вы анимацию, или она сама окончилась, promise-объект решает, что операция успешно завершена. Отмена анимации, другими словами, вызывает обработчик завершения promise-объекта, а не его обработчик ошибки.
Помните, что так как все WinJS-анимации реализованы с помощью CSS, они не начинаются, пока вы не передадите управление обратно в поток пользовательского интерфейса. Это означает, что вы можете настроить несколько анимаций, зная, что они начнутся примерно в одно и то же время, как только вы вернетесь из функции. Поэтому, хотя методы анимации возвращают promise-объекты, они не похожи на другие асинхронные операции WinRT, которые начинают исполняться в другом потоке.
В любом случае, давайте взглянем на код! В самом простом случае все, что вам нужно сделать – это вызвать один из методов анимации и анимация будет исполнена, когда вам нужно. Сценарий 6 примера, например, просто добавляет следующие обработчики к событиям MSPointerDown и MSPointerUp трех разных элементов (js/pointerFeedback.js):
function onPointerDown(evt) { WinJS.UI.Animation.pointerDown(evt.srcElement); } function onPointerUp(evt) { WinJS.UI.Animation.pointerUp(evt.srcElement); }
Нам обычно не нужно что-либо делать, когда анимация завершится, поэтому нам не нужно вызывать done или предоставлять завершающую функцию. На самом деле, использование многих из анимаций так же просто.
Анимация crossFade, в свою очередь (Сценарий 10), работает с двумя элементами: появляющимся и исчезающим (все они должны быть видимыми и являться частями DOM в процессе анимации, учтите!). Вызов анимации выглядит так: (js/crossfade.js):
WinJS.UI.Animation.crossFade(incoming, outgoing);
Но это еще не все. Обычная возможность анимаций заключается в том, что вы можете предоставить массив элементов, на которых должна быть исполнена одна и та же анимация, или, в случае с crossfade, два массива элементов. В то время, как это не особенно удобно для анимаций наподобие pointerDown и pointerUp (каждое событие указателя следует обрабатывать независимо), это, без сомнения, удобно для большинства остальных.
Рассмотрим анимацию enterPage. В своей единственной форме она принимает элемент для анимации и необязательное начальное смещение, где должен находиться элемент по отношению к своей конечной позиции. (Вообще говоря, вы вам следует опустить это смещение, если оно вам не нужно, так как это улучшает производительность – в примере туда передается null, что я опустил в нижеприведенном коде). Анимация enterPage так же может принимать коллекцию элементов, такую, как результат работы querySelectorAll. Сценарий 1 (html/enterPage.html и js/enterPage.js) предоставляют возможность выбора того, сколько элементов нужно анимировать раздельно:
switch (pageSections) { case "1": // Анимировать всю страницу enterPage = WinJS.UI.Animation.enterPage(rootGrid); break; case "2": // Анимировать заголовок и тело страницы enterPage = WinJS.UI.Animation.enterPage([[header, featureLabel], [contentHost, footer]]); break; case "3": // Анимировать заголовок, области ввода и вывода enterPage = WinJS.UI.Animation.enterPage([[header, featureLabel], [inputLabel, input], [outputLabel, output, footer]]); break; }
В то время, как аргумент элемента – это массив, аргумент смещения, если он задан, может быть либо отдельным смещением, которое применяется ко всем элементам, либо массивом, задающим отдельные смещения для каждого элемента. Каждое смещение – это объект, свойства которого определяют смещение. Смотрите js/dragBetween.js для Сценария 13, где этот подход используется с анимацией dragBetweenEnter:
WinJS.UI.Animation.dragBetweenEnter([box1, box2], [{ top: "-40px", left: "0px" }, { top: "40px", left: "0px" }]);
Вот модификация, где одно и то же смещение применяется к обоим элементам:
WinJS.UI.Animation.dragBetweenEnter([box1, box2], { top: "0px", left: "40px" });
Сценарий 4 (js/transitioncontent.js) показывает, как вы можете объединить в цепочку два promise-объекта для выполнения перехода между двумя различными блоками содержимого:1Обратите внимание на то, что в примере переменная output передается в качестве первого параметра в exitContent и enterContent. код должен выглядеть как показано здесь, с outgoing, который передается в exitContent и incoming, который передается в enterContent.
WinJS.UI.Animation.exitContent(outgoing, null).done( function () { outgoing.style.display = "none"; incoming.style.display = "block"; return WinJS.UI.Animation.enterContent(incoming, null); });
Происходящее становится немного интереснее, когда мы посмотрим в методы анимации create*, которые касаются так называемой анимации макета (layout animation), для выполнения добавления и удаления элементов в списке, разворачивания и сворачивания содержимого и так далее. Каждый из них предусматривает трехступенчатую обработку, где вы создаете анимацию, выполняете манипуляции с DOM, и затем исполняете анимацию, как показано в Сценарии 7 (js/addAndDeleteFromList.js):
// Создать анимацию addToList. var addToList = WinJS.UI.Animation.createAddToListAnimation(newItem, affectedItems); // Вставить новый элемент в дерево DOM. Элементы, на которые это повлияло, поменяют положение. list.insertBefore(newItem, list.firstChild); // Исполнить анимацию. addToList.execute();
Причина для трехступенчатой обработки заключается в применении анимацию к только что добавленному элементу, или элементу, который убирают из списка, всем им нужно присутствовать в DOM во время исполнения анимации. Такой порядок работы позволяет вам создавать анимацию на основе начального состояния содержимого, манипулировать DOM (или просто устанавливать стили, и так далее) для создания конечного состояния и затем исполнять анимацию, позволив всему встать на свои места. Вы затем можете использовать метод done promise-объекта, возвращенного из execute для выполнения любой окончательной обработки. Сценарий 5 (js/expandAndCollapse.js) разьясняет это:
// Создание анимации сворачивания. var collapseAnimation = WinJS.UI.Animation.createCollapseAnimation(element, affected); // Удаляем сворачивающийся элемент из содержимого документа, таким образом, зависимые элементы меняют // положение. Не удаляйте сворачиваемый элемент из DOM и не скрывайте его в данный момент, в противном случае // анимация этого элемента не будет выполнена. element.style.position = "absolute"; element.style.opacity = "0"; // Исполнение анимации сворачивания. collapseAnimation.execute().done( // После завершения анимации (или если произошла ошибка), скрываем элемент. function () { element.style.display = "none"; }, function () { element.style.display = "none"; } );
В качестве последнего примера, так как я знаю, что вы достаточно сообразительны для того, чтобы самостоятельно посмотреть другие варианты, хочу показасть Сценарий 21 (js/customAnimation.js). Здесь показано, как использовать методы WinJS.UI.executeAnimation и WinJS.UI.executeTransition:
function runCustomShowAnimation() { var showAnimation = WinJS.UI.executeAnimation( target, { // Обратите внимание: этот ключевой кадр относится к ключевому кадру, определенному в customAnimation.css. // Если он не определен в CSS, анимация не будет работать. keyframe: "custom-opacity-in", property: "opacity", delay: 0, duration: 500, timing: "linear", from: 0, to: 1 } ); } function runCustomShowTransition() { var showTransition = WinJS.UI.executeTransition( target, { property: "opacity", delay: 0, duration: 500, timing: "linear", to: 1 } ); }
Если вы хотите объединить несколько анимаций (как делается во многих анимациях WinJS), отметим, что обе эти функции возвращают promise-объекты, в итоге, вы можете комбинировать несколько результатов с помощью WinJS.Promise.join и использовать один обработчик завершения, который выполняет пост-обработку. Это именно то, что происходит внутри WinJS.
И если вы уже знаете что-либо о CSS-анимациях и переходах, вы, возможно, можете сказать, что объекты, которые вы передаете executeAnimation и executeTransition – это просто краткие наименования стилей CSS, которые вы используете. Коротко говоря, эти методы дают вам простой способ создавать собственные пользовательские анимации и переходы, пользуясь возможностями CSS. Посмотрим сейчас непосредственно на эти возможности.
Врезка: параллактическая и панорамная анимации
Разработчики часто спрашивают, как создать параллактическую или панорамную анимацию фона, как на Начальном экране Windows. Если вы не знакомы с этой концепцией, перейдите на Начальный экран и прокрутите его, обращая внимание на то, что фон так же прокручивается, но медленнее, чем плитки. Это создает ощущение, будто плитки плавают над фоном.
Хотя в JavaScript можно реализовать подобный эффект (смотрите пример "KidBook" (http://ie.microsoft.com/testdrive/HTML5/KidsBook/Default.html) на сайте Internet Explorer TestDrive), мы не рекомендуем этого делать, или, по крайней мере, рекомендуем предусмотреть параметр, позволяющий отключить его. Речь идет о том, что особенности работы с потоками и модель отрисовки в JavaScript могут привести к прерывистым перемещениям, за исключением работы подобной анимации на мощных устройствах. Этот эффект будет ярко выражен на маломощных компьютерах, особенно на ARM-устройствах. Кроме того, подобная анимация может потреблять слишком много ресурсов процессора и нерационально использовать батареи.
Это – один из случаев, где использование C++ и DirectX (или даже C#/VB и XAML) имеет явное преимущества перед JavaScript, и это нужно принять во внимание, если подобный эффект в вашем приложении абсолютно необходим.