Сочетайте легкость входа и легкость выхода

У меня есть два элемента с анимацией. Анимация первых элементов длится 1 секунду и имеет кривую замедления cubic-bezier(0.5, 0, 0.5, 1) (замедление). Анимация второго элемента начинается 0.5s после первого, длится 0.5s и имеет кривую замедления cubic-bezier(0, 0, 0.5, 1) (только замедление). Я думал, что это позволит их анимации совпадать, поскольку обе кривые заканчиваются одинаковым замедлением, но первый элемент в середине движения движется немного быстрее, чем второй. Как сделать так, чтобы анимация совпадала? И если уж на то пошло, как мне совместить любую кривую замедления с кривой плавности входа/выхода в аналогичной ситуации?

Пример проблемы:

const first = document.getElementById("first");
const second = document.getElementById("second");

function animateit() {
  first.classList.add("animated");
  setTimeout(() => second.classList.add('animated'), 500)
}

function reset() {
  first.classList.remove('animated');
  second.classList.remove('animated');
}
* {
  margin: 5px;
}

div {
  width: 50px;
  height: 50px;
  background: black;
}

#first {
  transition: 1s cubic-bezier(0.5, 0, 0.5, 1);
}

#second {
  transition: 0.5s cubic-bezier(0, 0, 0.5, 1);
  transform: translateX(100px);
}

.animated {
  transform: translateX(200px) !important;
}
<button onclick='animateit()'>animate</button>
<button onclick='reset()'>reset</button>
<div id='first'></div>
<div id='second'></div>

Я попробовал изменить ослабление второго элемента и очень близко подошел к cubic-bezier(0.3, 0.6, 0.5, 1), но оно все равно не полностью совпадает.

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

Mike 'Pomax' Kamermans 22.06.2024 23:57
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Введение в CSS
Введение в CSS
CSS является неотъемлемой частью трех основных составляющих front-end веб-разработки.
Как выровнять Div по центру?
Как выровнять Div по центру?
Чтобы выровнять элемент <div>по горизонтали и вертикали с помощью CSS, можно использовать комбинацию свойств и значений CSS. Вот несколько методов,...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
CSS: FlexBox
CSS: FlexBox
Ранее разработчики использовали макеты с помощью Position и Float. После появления flexbox сценарий полностью изменился.
1
1
53
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Кривые Безье нелинейны, поэтому, если вы возьмете две кривые, одна из которых начинается в A и заканчивается в B, а другая начинается с (A+B)/2 и заканчивается в B, обе с правилами облегчения выхода. , то обе кривые будут иметь максимальную скорость в средней точке, но кривые не будут выглядеть одинаково и не будут совпадать по скорости:

0.5, 0, 0.5, 1 выглядит так:

Итак, если нам нужна кривая, которая захватывает только «вторую половину этой кривой», то есть этот раздел:

тогда нам нужно разделить кубическую кривую в этой средней точке. В результате мы получим две кривые, первая из которых нас не волнует, а вторая начинается с (0,5,0,5), имеет первую контрольную точку (0,625, 0,75), вторую контрольную точку (0,75). , 1) и его конечная точка в (1,1):

Это неправильное начало для целей CSS, но кривые Безье инвариантны к масштабированию (пока мы масштабируем x и y на одинаковую величину), поэтому мы можем просто масштабировать все координаты так, чтобы получить кривую, которая начинается с (0 ,0), с первой контрольной точкой в ​​(0,25, 0,5), второй контрольной точкой в ​​(0,5, 1), а затем заканчивающейся в (1,1), точно так, как того требует CSS:

#second {
  transition: 0.5s cubic-bezier(0.25, 0.5, 0.5, 1); /* heck yeah! */
  transform: translateX(100px);
}

Мы посчитали, вперед! Все, что нам нужно сделать, это обновить кубические значения, и все готово!

Вот только… не совсем? Если мы просто воспользуемся этими значениями (и вы уже сталкивались с этим), то мы увидим, что все работает только «иногда», но почти всегда тайминги сбиваются и все вообще не совпадает:

const first = document.getElementById("first");
const second = document.getElementById("second");

function animateit() {
  first.classList.add("animated");
  setTimeout(() => second.classList.add('animated'), 500)
}

function reset() {
  first.classList.remove('animated');
  second.classList.remove('animated');
}
* {
  margin: 5px;
}

div {
  width: 50px;
  height: 50px;
  background: black;
}

#first {
  transition: 1s cubic-bezier(0.5, 0, 0.5, 1);
}

#second {
  transition: 0.5s cubic-bezier(0.25, 0.5, 0.5, 1);
  transform: translateX(100px);
}

.animated {
  transform: translateX(200px) !important;
}
<button onclick='animateit()'>animate</button>
<button onclick='reset()'>reset</button>
<div id='first'></div>
<div id='second'></div>

Что происходит?

Как оказалось: JavaScript работает, и мы не хотим, чтобы он: setTimeoutsetInterval) крайне ненадежны с точки зрения того, когда они сработают, потому что единственная гарантия, которую вы получите, это то, что они Я выстрелю «как минимум через X миллисекунд»… но не более того. Если setTimeout на 500 мс занимает 501 мс, это вполне соответствует спецификации. Если это занимает 510 мс, это тоже в пределах спецификации. И если на стрельбу уходит час, это все равно в пределах нормы. Мы не можем использовать таймеры JS, если нам нужно, чтобы события запускались в определенное время. В JS просто нет ничего, что можно было бы сделать.

(по крайней мере, без некоторых суперкреативных лайфхаков)

К счастью, CSS предлагает нам очень удобный инструмент, который позволяет нам полностью обойти эту проблему: вместо запуска таймеров мы можем просто использовать CSS-анимацию и воспользоваться правилом задержки анимации. Мы можем просто настроить CSS так, чтобы у нас была одинаковая анимация для обоих блоков, начиная с «их начальной позиции» (для которой мы можем использовать переменную CSS: 0 пикселей для первого блока и половину расстояния до конца для второго блока). ) до «конечной позиции» (что составляет 200 пикселей для обоих, но я собираюсь сделать это 400 пикселей, чтобы эффект был еще более очевидным):

#group {
  --base: 0px;
  --end: 400px;

  .box {
    transform: translateX(var(--base));
  }

  .second {
    --base: calc(var(--end) / 2 );
  }
}

Затем мы указываем первому блоку анимацию в течение 1 секунды, начиная немедленно, и мы приказываем второму блоку анимировать полсекунды, подождав полсекунды:

.first {
  animation-name: move;
  animation-duration: 1s;
  animation-timing-function: cubic-bezier(.5, 0, .5, 1);
}

.second {
  animation-name: move;
  animation-delay: 0.5s;
  animation-duration: 0.5s;
  animation-timing-function: cubic-bezier(.25, .5, .5, 1);
}

И если мы это сделаем, то теперь все пойдет идеально, точно так, как и предполагалось:

animate.addEventListener(`click`, () => {
  group.classList.add(`animate`);
  setTimeout(() => group.classList.remove(`animate`), 3000);
});
* {
  margin: 5px;
}

#group {
  --base: 0px;
  --end: 400px;
  .box {
    width: 50px;
    height: 50px;
    background: black;
    transform: translateX(var(--base));
    &.second {
      --base: calc(var(--end) / 2);
    }
  }
  &.animate {
    .box {
      animation-name: move;
      animation-fill-mode: forwards;
    }
    .first {
      animation-duration: 1s;
      animation-timing-function: cubic-bezier(.5, 0, .5, 1);
    }
    .second {
      animation-delay: 0.5s;
      animation-duration: 0.5s;
      animation-timing-function: cubic-bezier(.25, .5, .5, 1);
    }
  }
}

@keyframes move {
  from {
    transform: translateX(var(--base));
  }
  to {
    transform: translateX(var(--end));
  }
}
<button id = "animate">animate</button>

<div id = "group">
  <div class='first box'></div>
  <div class='second box'></div>
</div>

Большое спасибо за ответ и за то, что сообщили мне о проблеме синхронизации JS. Я понятия не имел, что это настолько неточно! Кстати, после того, как вы отредактировали ответ, заметил небольшую опечатку в ссылке по поводу разделения кривых. Там была добавлена ​​лишняя буква «Т», поэтому я пошел дальше и исправил ее.

CatHat 23.06.2024 00:38

Stackoverflow не позволял мне сохранять изменения, если я не добавлял более 6 символов, поэтому мне также пришлось добавить описания к изображениям xD

CatHat 23.06.2024 00:52

Другие вопросы по теме