Переходы и анимация.

У многих начинающих разработчиков на Angular камнями преткновения становятся лень, страх оказаться в незнакомой ситуации и предстать в глупом виде перед своими сверстниками или анимация. А у тех, кто поборол лень и страх, познакомился с анимацией и начал углубляться в адскую кухню более сложных проектов, часто возникают вопросы для решении более специфических задач. Рассмотрим рецепт приготовления решения для одной распространенной задачи, а именно - анимации постраничного перехода.

Немного об ингредиентах нашего рецепта демо-проекта

В основном компоненте у нас есть небольшая навигация и routing-outlet для организации вывода страниц.

И плюс два компонента с контентом которые будут служить нам страницами.

Для начала нам потребуется модуль BrowserAnimationsModule -- 1шт.
Подключим его в файле app.module.ts.

Не стоит забывать, что для корректной работы в браузерах, которые не поддерживают Web Animations API, нужен полифил web-animations .

Для этого, в консоле проекта пропишем: npm install --save web-animations-js и в файле polyfills.ts раскомментируем строку:

И после этого подключаем его в основном модуле:


Добавляем заранее приготовленную анимацию по вкусу:)

Для начала немного подкорректируем наш роутинг чтоб при переходе между страницами мы еще и передавали состояние для анимации (state):

После чего в основном компоненте app.component.ts добавим функцию для получения этого состояния getState(), а также подключим и задекларируем нашу анимацию:

Теперь забиндим анимацию и подвяжем ее под результат функции getState() в основном темплейте:

На данном этапе может возникнуть вопрос “зачем?” Ведь у нас, в анимации stateChangeExpr прописан как: "* <=> *", а это по сути любая смена состояний. Но как показывает практика, в проектах редко бывает только одна анимация. Множество разных анимации переходов по страницах как раз будут основаны на состояниях (state). Вам нужно будет прописывать только stateChangeExpr для нужного перехода.

Рассмотрим один из примеров. Допустим вам нужна уникальная анимация только при переходе с страницы “контакты” на страницу “новостей”. В роутинге вы будете передавать state со значениями “contacts” и “news” соответственно. Тогда значение stateChangeExpr в анимации для этого конкретного случая будет "contacts => news".

На данном этапе у нас уже есть почти готовое блюдо:

Но идеальное блюдо требует не только идеального приготовления, но и внимания к деталям.

По-этому, на определенные объекты пропишем дополнительную анимацию. Пусть это будут абзацы на странице Page1.

Добавляем к ним класс:

После этого дописываем анимацию.

В 6 строке мы описали статическое поведение наших абзацев при переходе на страницу с ними. А в строках 16-20 поведение абзацев после перехода на страницу.

В нашем рецепте мы использовали stagger() , который с заданным интервалом переберет наши абзацы и применит к ним анимацию.

Хочу обратить внимание на то, что при переходе на другие страницы, элементов с классом .anim может не быть. А при компиляции query(":enter .anim" выдаст ошибку, именно поэтому нужно использовать надстройку { optional: true } которая будет игнорировать отсутствующую выборку.

Теперь можем любоваться супер-блюдом, которое у нас получилось:)

В заключение скажу, что на основании вышеизложенного можно создавать огромные каскады анимированных переходов на ваших проектах. Где всё будет зависеть только от вашего воображения или пожеланий дизайнера.

В общем на этом все, надеюсь материал был полезен.

Bon appétit!

** На момент написания статьи использовался @angular/cli: 1.7.3 с последней версией Angular 5.2.9. Код также будет поддерживаться и на версиях Angular 2 и выше.
Отдельная благодарность Gerard Sans за содействие при написании статьи.

В CSS3 есть две техники визуализации, которые соревнуются за ваше внимание: анимация (Animations) и переходы (Transitions) (примечание: в статье будут использованы русские варианты названий этих техник). В этой статье мы попробуем выявить сходства и различия этих техник, чтобы вы понимали, когда лучше использовать тот или иной способ.

Для правильного понимания смысла данной статьи, очень желательно, чтобы вы уже имели опыт в работе с анимацией и переходами.

Сходства

Если смотреть невооруженным глазом, то кажется, что различий между анимацией и переходами нет. Обе техники позволяют:

  • определять CSS-свойства, которые мы хотим изменять;
  • устанавливать вид математической функции для управления скоростью перехода от одного значения к другому;
  • указывать продолжительность перехода или анимации;
  • перехватывать события анимации и переходов и делать с ними все, что угодно;
  • визуализировать изменения CSS-свойств.

На этом сходства заканчиваются и начинаются…

Различия

Различия между анимацией и переходами проявляются, когда вы начинаете их запускать. Различия также состоят в том, насколько легко/сложно запрограммировать переход/анимацию и тем, как легко всем этим управлять через JavaScript . Давайте исследуем эти различия более подробно.

Запуск

Одним из основных различий между переходами и анимацией является то, как они вызываются.

Переход запускается, когда изменяется какое-либо CSS-свойство. Наиболее часто встречается использование псевдокласса:hover для изменения значения свойства CSS:

В приведенном выше примере используется переход, а результатом будет расширение круга на определенную величину при наведении на него курсора мыши. Другим способом запуска перехода является использование JavaScript, чтобы программным путем добавлять или удалять CSS-классы, симулируя изменение значений свойств CSS. И, наконец, можно с помощью JavaScript задать inline-стиль меняющий CSS-свойство, которое «слушает » ваш переход.

Анимация же, в отличие от переходов, не требует явного запуска. Если анимация была определена, то она начинает работать автоматически.

С помощью имеющегося свойства, анимацию можно перевести в статус паузы (pause ), что означает, что она будет неактивна до тех пор, пока свойство не будет переведено в статус запущено (running ). Консорциум W3C решил удалить это свойство, поэтому сейчас его уже не существует.

Зацикливание

Это очень просто. Анимация может быть легко зациклена с помощью установки свойства animation-iteration-count. Вы можете задать любое фиксированное число для повтора анимации, которое пожелаете:

animation-iteration-count: 5;

Если вы хотите, чтобы ваша анимация повторялась бесконечно, то установите следующее значение:

animation-iteration-count: infinite;

Переходы, с другой стороны, не имеют свойства, которое определяет количество повторов. После запуска, переход повторяется только один раз. Вы можете зациклить переход с помощью события transitionEnd, но по сравнению с анимацией это далеко не так просто.

Определение промежуточных точек / ключевых кадров (keyframes)

С помощью анимации вы можете определить ключевые кадры, которые дадут вам больше контроля над значениями CSS-свойств, нежели просто начальная и конечная точка:

Вы можете задать любое количество ключевых кадров. В процессе воспроизведения анимации, каждый кадр будет изменять нужным образом значения свойств. Это позволяет вам реализовывать разнообразные виды анимации, которые позволяют HTML5 соревноваться наравне с такими анимационными технологиями, как Flash, и даже быть эффективнее.

При использовании переходов у вас нет достаточного контроля над конечным результатом:

Переход происходит из начальной точки в конечную. Вы не можете определить промежуточные точки, как в случае с анимацией, поэтому переход может быть не слишком удачным выбором, если вы хотите сложную анимацию, вроде вот этой.

Явное определение анимируемых свойств

Следующая вещь, о которой я хочу рассказать, это явное определение анимации и переходов применительно к значениям CSS-свойств.

С формальной точки зрения, если у вас есть переход, то каждое CSS-свойство, которое вы хотите с помощью него анимировать, должно быть явно определено.

Например, у вас есть следующий переход:

#mainContent { background-color: #CC0000; transition:background-color .5s ease-in; } #mainContent:hover { cursor: pointer; background-color: #000000; width:500px; }

В коде выше, указаны различные значения свойств background-color и width . Однако, явно определен переход только для свойства background-color. Это значит, что браузер будет анимировать только изменение свойства background-color.

Если же необходимо, чтобы анимированы были оба свойства — и background-color , и width — следует это явно определить:

#mainContent { background-color: #CC0000; transition:background-color .5s ease-in, width .5s ease-in } #mainContent:hover { cursor: pointer; background-color: #000000; width: 500px; }

Что насчет параметра «all» при определении перехода?

В действительности, нет необходимости определять каждое анимируемое свойство при объявлении перехода. Вы можете облегчить себе жизнь, если будете использовать значение «all » вместо описания конкретных свойств, например: transition: all .5s ease-in . Однако так поступать не рекомендуется, потому что такой подход наносит ущерб производительности.

В этом случае браузер наблюдает за всеми свойствами, которые могут быть анимированы, вместо того, чтобы следить за явно указанными. Если у вас нет необходимости в использовании параметра «all », то рекомендуется указывать все анимируемые свойства по отдельности.

Используя анимацию, вы можете определять промежуточные значения свойств с помощью ключевых кадров так, как вам это заблагорассудится:

keyframes imageSlide { 0% { left: -150px; } 20% { left: 50px; height: 200px; } 80% { left: 200px; height:300px; } 100% { left: 600px; background-color:#FFFFFF; } }

В примере выше, свойства height и background-color будут плавно изменяться в соответствии с определенными ключевыми кадрами, даже если какое-то свойство до этого не было объявлено!

Взаимодействие с JavaScript

В большинстве случаев, переходы и анимация достаточно хорошо выполняют свои задачи. Вы определяете начальную и конечную, а также промежуточные точки, значения которых должны принимать CSS-свойства. Ваша анимация или переход будет считывать эти значения и заботится о том, чтобы все было точно выполнено согласно инструкциям.

Этот сценарий лучше всего работает, когда вы точно знаете, какого результата хотите достичь. Однако, может случиться так, что вам потребуется изменять значения, используемые для анимации, согласно какому-то внешнему источнику — к примеру клику мыши, результату внешнего вычисления и тому подобное.

Для такого взаимодействия не подходят величины, заранее определенные в CSS. Можно положиться на использование JavaScript, но полностью реализовывать анимацию на нем, задача достаточно сложная. Наиболее оптимальным решением в данной ситуации будет использование гибридного подхода. Это означает, что анимация или переход объявляются средствами CSS, но некоторые аспекты динамически изменяются посредством JavaScript.

Когда дело доходит до взаимодействия JavaScript и анимации/переходов, то вы почти всегда будете использовать именно переходы. При связке анимация-JavaScript… практически невозможно заставить анимацию нормально работать.

Анимация очень специфична для создания связки с JavaScript. Команда @keyframes четко определяет последовательность анимации, которая запускается сразу после загрузки страницы. Попытка изменить заданную анимацию через JavaScript потребует выполнения очень сложной последовательности шагов, включая модификацию самого стилевого правила @keyframes. Если вы когда-нибудь пытались изменять CSS-свойства внутри стилевого правила, то вы, скорее всего, представляете сложность данной процедуры.

Противоположностью анимации в данном смысле является переход. Переходы не настолько четко определены. Переход запускается, когда определенное свойство изменяется. Изменение этого свойства может быть полностью задано через CSS:

#myElement { background-color: #FFF; transition: background-color .2s ease-in; } #myElement:hover { background-color: #000; }

Это изменение может быть выполнено через JavaScript: вы можете изменить CSS-свойство, которое «слушает » ваш переход, обратившись к inline-стилю :

var myElement = document.querySelector("#myElement"); myElement.style.backgroundColor = "333";

Переход не обращает внимания на то, каким способом изменяются значения «слушаемых» свойств. Пока значение изменяется, переход будет работать. Это значит, что вы можете делать много интересных вещей с интерактивными сценариями, не связанными жестко заданными начальными и конечными точками. Желаемые значения свойств для перехода могут быть заданы через JavaScript.

Чтобы лучше понять, о чем идет речь, посмотрите вот этот простой пример :

Кликните в любой точке серой формы и круг переместится в эту точку.

Этот пример работает достаточно просто. Перемещение круга обрабатывается с помощью CSS-перехода. Однако, координаты точки, в которую надо переместить круг, передаются через JavaScript. Так как переход «слушает » координаты, то любое их изменение ведет к запуску этого перехода. Конечный результат работы именно такой, каким он и был задуман.

Самое примечательное, что не требуется писать JavaScript-код для реализации самого перехода. И, наконец, так как поддержка переходов встроена в стандарт CSS и, соответственно, в браузер, анимация происходит очень плавно.

Эта дружба между переходами и JavaScript очень сильна, поэтому вам стоит использовать это преимущество на полную катушку.

Post Views: 3 215

Переходы в приложениях Material Design обеспечивают визуальную целостность. Когда пользователь перемещается по приложению, различные его элементы меняют своё состояние. Движение и трансформация укрепляют идею о том, что интерфейс осязаемый и соединяет общие элементы представлений.

В этой статье мы продемонстрируем, как реализовать переход из изображения в RecyclerView на одной активности в изображение в ViewPager в другой активности, используя Shared Elements для того, чтобы определить, какие представления участвуют в процессе перехода и как. Мы также рассмотрим сложный случай обратного перехода в список к элементу, которого ранее не было на экране.

Вот результат, которого мы собираемся достичь:

Что такое общие элементы?

Переход общего элемента определяет, как происходит анимация элемента, который присутствует в двух активностях, между ними. Например, изображение, которое отображается в ImageView на активностях А и Б , переходит от активности А к активности Б когда Б становится видимой.

Реализация перехода от RecyclerView к ViewPager

Ниже приведена анимация перехода общего элемента из RecyclerView в ViewPager.

Для начала откроем файл res/values-v21/styles.xml и в свойствах темы зададим windowsContentTransitions а анимацию.

true @transition/change_image_transform

Теперь определим анимацию, создав файл change_image_transform.xml в папке res/transition .

RecyclerView и ViewPager используют для своей работы адаптер, поэтому они не могут установить transitionName в разметке XML. Нужно использовать View.setTransitionName() и View.setTag() , чтобы динамически установить transitionName и tag соответственно во время привязки представления к адаптеру. Поэтому в метода адаптера onBindViewHolder() добавим следующий код:

@Override public void onBindViewHolder(@NonNull final MyViewHolder holder, int position) { holder.image.setTag(position); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { holder.image.setTransitionName(holder.image.getContext().getString(R.string.transition_name, position)); // в качестве примера ресурсу R.string.transition_name задано значение name%1$d } }

Здесь поле image — это ImageView, анимацию перехода которого мы собираемся реализовать. В результате каждому элементу будет задан transitionName, равный nameX , где X — позиция элемента в списке.

Примечание: в нашем примере будут использоваться следующие имена активностей: SourceActivity для активности с RecycleView и DestinationActivity для активности с ViewPager.

Важно понимать, что анимация перехода общего элемента требует связь один-к-одному для определения анимации. Важны также следующие моменты:

  • Во-первых, каждое представление должно иметь уникальный transitionName .
  • Во-вторых, нужно подождать, пока ViewPager начнёт показывать изображения, прежде чем включать анимацию.

Теперь нужно реализовать обработку нажатия на элемент. При нажатии будет открываться вторая активность, в которую будут передавать созданные имя и тег. Для этого пробросим в адаптер слушатель из активности.

Interface ClickListener { void onItemClick(ImageView image); } private ClickListener listener; ... RecyclerAdapter(List photoList, ClickListener listener) { this.photoList = new ArrayList<>(photoList); this.listener = listener; } ... @Override public void onBindViewHolder(@NonNull final MyViewHolder holder, int position) { final int photo = photoList.get(position); holder.image.setImageResource(photo); holder.image.setTag(position); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { holder.image.setTransitionName(holder.image.getContext().getString(R.string.transition_name, position)); } holder.image.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { listener.onItemClick(holder.image); } }); }

В активности же создадим экземпляр этого слушателя и переопределим метод onItemClick() .

@Override protected void onCreate(Bundle savedInstanceState) { ... recyclerView.setAdapter(new RecyclerAdapter(photoList, new RecyclerAdapter.ClickListener() { @Override public void onItemClick(ImageView image) { Intent intent = new Intent(SourceActivity.this, DestinationActivity.class); intent.putExtra("current", (int) image.getTag()); intent.putIntegerArrayListExtra("list", photoList); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(SourceActivity.this, image, image.getTransitionName()); startActivity(intent, options.toBundle()); } else { startActivity(intent); } } })); }

Здесь основным моментом является использование объекта ActivityOptions , в который с помощью метода makeSceneTransitionAnimation() передаются контекст, представление, которое нужно анимировать, и его transitionName.

После этого стартует интент и мы переходим на активность с ViewPager . Как уже говорилось выше, нельзя сразу при старте активности запускать анимацию, поскольку ViewPager нужно время, чтобы сгенерировать элементы. Поэтому перед вызовом setContentView() в методе onCreate() добавим вызов метода postponeEnterTransition() , который запрещает переход общего элемента.

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { postponeEnterTransition(); } setContentView(R.layout.activity_destination); ... }

После этого начинается инициализация ViewPager и передача данных в адаптер. В адаптере нам нужно задать те же transitionName и tag, что был у элемента в RecyclerView, после чего вызвать ме

@Override @NonNull public Object instantiateItem(@NonNull ViewGroup collection, int position) { int photo = photoList.get(position); LayoutInflater inflater = LayoutInflater.from(mContext); View v = inflater.inflate(R.layout.item, collection, false); ImageView img = v.findViewById(R.id.image); img.setImageResource(photo); collection.addView(v); String name = mContext.getString(R.string.transition_name, position); img.setTag(position); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { img.setTransitionName(name); if (position == current) { listener.setStartPostTransition(img); } } return v; }

Теперь, когда всё подготовлено, нам нужно запустить анимацию. Пробросим интерфейс в адаптер из активности, в котором с помощью метода setStartPostTransition() запустим анимацию, которая была прервана при старте активности.

@TargetApi(21) @Override public void setStartPostTransition(final View view) { view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { view.getViewTreeObserver().removeOnPreDrawListener(this); startPostponedEnterTransition(); return false; } }); }

В результате мы получим плавную анимацию перехода из RecyclerView в ViewPager.

Реализация перехода от ViewPager к RecyclerView

Теперь нужно осуществить обратную анимацию при возврате из DestinationActivity в SourceActivity. Основной проблемой здесь может являться то, что при возврате в первую активность не обязательно вернётся тот же элемент, что был при переходе. В таком случае нужно определить позицию нового элемента, сделать скролл, если необходимо, и воспроизвести анимацию.

В DestinationActivity нам нужно переопределить метод finishAfterTransition() , внутри которого нужно поместить в интент текущую позицию элемента.

@Override public void finishAfterTransition() { int pos = viewPager.getCurrentItem(); Intent intent = new Intent(); intent.putExtra("exit_position", pos); setResult(RESULT_OK, intent); if (current != pos) { View view = viewPager.findViewWithTag(pos); setSharedElementCallback(view); } super.finishAfterTransition(); } @TargetApi(21) private void setSharedElementCallback(final View view) { setEnterSharedElementCallback(new SharedElementCallback() { @Override public void onMapSharedElements(List names, Map sharedElements) { names.clear(); sharedElements.clear(); names.add(view.getTransitionName()); sharedElements.put(view.getTransitionName(), view); } }); }

Если текущая позиция не совпадает с той, которая была при старте активности, значит нужно вызвать метод setEnterSharedElements(), в котором обновляем список общих элементов, подменяя старый элемент на новый.

После этого активность закрывается и мы возвращаемся в SourceActivity . Здесь мы вызываем метод активности onActivityReenter() , чтобы поймать интент с позицией.

Public void onActivityReenter(int resultCode, Intent data) { super.onActivityReenter(resultCode, data); if (resultCode == RESULT_OK && data != null) { exitPosition = data.getIntExtra("exit_position", 0); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); View viewAtPosition = layoutManager.findViewByPosition(exitPosition); // Прокрутка к позиции, если вьюха для текущей позиции не null (т.е. // не является частью дочерних элементов layout-менеджера) или если // она видна не полностью. if (viewAtPosition == null || layoutManager.isViewPartiallyVisible(viewAtPosition, false, true)) { layoutManager.scrollToPosition(exitPosition); setTransitionOnView(); } // карточка видна, нужно поставить колбек else { setTransitionOnView(); } } } }

Если прокручивать не нужно или прокрутка завершена, начинаем воспроизведение анимации. Для этого вызывается метод setTransitionOnView() , в котором ожидаем окончания скролла и запускаем анимацию.

@TargetApi(Build.VERSION_CODES.LOLLIPOP) private static class CustomSharedElementCallback extends SharedElementCallback { private View mView; public void setView(View view) { mView = view; } @Override public void onMapSharedElements(List names, Map sharedElements) { names.clear(); sharedElements.clear(); if (mView != null) { String transitionName = ViewCompat.getTransitionName(mView); names.add(transitionName); sharedElements.put(transitionName, mView); } } } @TargetApi(Build.VERSION_CODES.LOLLIPOP) private void setTransitionOnView() { final CustomSharedElementCallback callback = new CustomSharedElementCallback(); setExitSharedElementCallback(callback); getWindow().getSharedElementExitTransition().addListener(new Transition.TransitionListener() { @Override public void onTransitionStart(Transition transition) { } @Override public void onTransitionPause(Transition transition) { } @Override public void onTransitionResume(Transition transition) { } @Override public void onTransitionEnd(Transition transition) { removeCallback(); } @Override public void onTransitionCancel(Transition transition) { removeCallback(); } private void removeCallback() { getWindow().getSharedElementExitTransition().removeListener(this); setExitSharedElementCallback(null); } }); postponeEnterTransition(); recyclerView.getViewTreeObserver() .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { recyclerView.getViewTreeObserver().removeOnPreDrawListener(this); RecyclerView.ViewHolder holder = recyclerView.findViewHolderForAdapterPosition(exitPosition); if (holder instanceof RecyclerAdapter.MyViewHolder) { callback.setView(((RecyclerAdapter.MyViewHolder) holder).image); } return true; } }); }

На этом всё. Теперь анимация обратного перехода будет работать даже в том случае, если элемент находился до этого вне поля видимости.

Исходный код примера можно посмотреть на GitHub, перейдя по

Paul is a Design and Perf Advocate

В приложении нередко бывает необходимо, чтобы пользователь выполнил переход от одного представления к другому. Это может быть переход из списка в представление с подробными данными или вывод на экран боковой панели навигации. Добавление анимационных эффектов к переходам между этими представлениями отлично подходит, чтобы удержать внимание пользователя и повысить визуальную привлекательность ваших проектов

TL;DR

  • Используйте свойство transition для перемещения между представлениями; не следует применять left , top или любое другое свойство, которое вызывает перерасчет макета.
  • Любая анимация должна быть мгновенной, ее продолжительность не следует делать большой.
  • Продумайте, как будут меняться анимационные эффекты и макеты при увеличении размера экрана; то, что подойдет для небольшого экрана может выглядеть неуклюже на экране большого размера.

То, как эти переходы между представлениями будут выглядеть и выполняться, в значительной степени зависит от типа представлений, с которыми вы имеете дело. Поэтому, например, анимация модального наложения поверх какого-либо представления будет отличаться от перехода от списка к представлению с подробными сведениями или наоборот.

Note: Нужно стремиться к тому, чтобы частота кадров любой анимации составляла 60 кадров в секунду. Это позволит избежать запинок при воспроизведении анимации, которые вряд ли понравятся вашим пользователям. Любому анимируемому элементу следует задать свойство will-change для всех компонентов, которые планируется изменять, задолго до того, как будет реализована сама анимация. Весьма вероятно, что для переходов между представлениями будет использоваться свойство will-change: transform

Использование атрибутов translation для перемещения между представлениями

Чтобы не усложнять, давайте предположим, что есть два представления: представление в виде списка и представление с подробными сведениями. После того как пользователь нажмет элемента списка в представлении списка, представление с подробными сведениями появится на экране, а представление списка исчезнет с него.

Чтобы достичь такого эффекта, вам потребуется контейнер для обоих представлений, которому будет задано свойство overflow: hidden . Таким образом, оба представления смогут находиться в контейнере одновременно без отображения горизонтальных полос прокрутки, при этом каждое представление будет при необходимости скользить из стороны в сторону внутри контейнера.

Код CSS для контейнера:

Container { width: 100%; height: 100%; overflow: hidden; position: relative; }

Положение контейнера задается как relative . Это означает, что каждое представление, находящееся внутри контейнера, можно разместить точно в левом верхнем углу, а затем перемещать с помощью свойства transform. Такой способ дает выигрыш с точки зрения производительности по сравнению с использованием свойства left (поскольку оно вызывает перерасчет макета и фактическую перерисовку страницы), кроме того, как правило, его проще рационализировать.

View { width: 100%; height: 100%; position: absolute; left: 0; top: 0; /* let the browser know we plan to animate each view in and out */ will-change: transform; }

Добавление transition к свойству transform обеспечивает привлекательный эффект скольжения. Чтобы скольжение вызывало приятные ощущения, необходимо использовать нестандартную кривую cubic-bezier , которая рассматривалась в документе [Руководство по изменению скорости при нестандартной анимации] (custom-easing.html).

View { /* Prefixes are needed for Safari and other WebKit-based browsers */ transition: -webkit-transform 0.3s cubic-bezier(0.465, 0.183, 0.153, 0.946); transition: transform 0.3s cubic-bezier(0.465, 0.183, 0.153, 0.946); }

Представление, которое исчезает с экрана, следует перемещать вправо, поэтому в данном случае представление с подобными сведениями необходимо двигать:

Details-view { -webkit-transform: translateX(100%); transform: translateX(100%); }

Var container = document.querySelector(".container"); var backButton = document.querySelector(".back-button"); var listItems = document.querySelectorAll(".list-item"); /** * Toggles the class on the container so that * we choose the correct view. */ function onViewChange(evt) { container.classList.toggle("view-change"); } // When you click on a list item bring on the details view. for (var i = 0; i < listItems.length; i++) { listItems[i].addEventListener("click", onViewChange, false); } // And switch it back again when you click on the back button backButton.addEventListener("click", onViewChange);

Наконец, добавляем декларации CSS для этих классов.

View-change .list-view { -webkit-transform: translateX(-100%); transform: translateX(-100%); } .view-change .details-view { -webkit-transform: translateX(0); transform: translateX(0); }

Этот код можно изменить, включив в него несколько представлений, но основная идея при этом останется неизменной. Каждое невидимое представление должно находиться за пределами экрана и появляться на нем только при необходимости, при этом представление, которое в данный момент находится на экране, должно с него исчезать.

Note: Создание такой иерархии, которая будет работать в разных браузерах, может быть очень сложной задачей. Например, в iOS для повторного включения fling-прокрутки требуется дополнительное свойство CSS, -webkit-overflow-scrolling: touch , однако при этом невозможно контролировать, для какой оси эта прокрутка будет работать (тогда как стандартное свойство overflow позволяет это делать). Обязательно тестируйте работу своего кода на различных устройствах!

Помимо перехода между представлениями, этот метод также можно применять для других элементов, выводимых на экран, таких как боковые элементы навигации. Разница заключается лишь в том, что другие представления при этом перемещать не нужно.

Обеспечение работы анимации на больших экранах

На экранах большого размера представление списка вообще не следует убирать с экрана, а представление с подробными сведениями следует выводить на экран с правой стороны. Это практически тоже самое, что и открытие представления навигации.