Принципы вертикальной прокрутки элементов SVG, чтобы они выглядели параллаксными?

#css #reactjs #animation #svg #parallax

Вопрос:

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

введите описание изображения здесь

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

введите описание изображения здесь

Когда вы прокручиваете страницу вниз, она показывает больше вертикальной сцены. НО он не просто прокручивает изображение вниз, как обычно. Допустим, луна остается в поле зрения в течение нескольких «страниц» прокрутки, только очень слегка оживая. Затем бум, вы достигаете определенной точки, и луна быстро исчезает из поля зрения, и вы оказываетесь в горах. Он медленно прокручивается через горы, а затем быстро несется к озеру. Каждый раз, когда он «медленно прокручивает» что-то, остается место для наложения некоторого содержимого. Независимо от того, насколько длинным будет содержимое для каждой «части», зависит, насколько медленной будет прокрутка этой части сцены. Таким образом, даже несмотря на то, что луна может быть, скажем, 500 пикселей, контент может стоить 3000 пикселей, поэтому он должен прокручивать, скажем, 200 пикселей SVG луны в фазе Луны, когда он прокручивает 3000 пикселей содержимого спереди. Затем он прокручивает оставшиеся 300 пикселей плюс еще немного, возможно, чтобы пройти мимо Луны, а затем медленно прокручивается через горы, скажем, с содержимым 10000 пикселей. И т.д.

Затем, когда вы находитесь в горах, каждый слой гор «на расстоянии» движется немного медленнее, чем тот, что впереди. Что-то в этом роде.

Вопрос в том, как мне разделить компоненты пользовательского интерфейса / SVG / код, чтобы создать эти эффекты? Там, где я сейчас нахожусь, у меня есть SVG, в котором есть тонны transform="matrix(...)" всего через каждый элемент, вот так:

 <g transform="matrix(1.27408,0,0,1.58885,-245.147,-3069.76)">
  <rect x="-430" y="4419.39" width="3572" height="1846.61" fill="blue" />
</g>
 

Поэтому мне нужно вставить некоторые значения в поле 5-го и 6-го параметров в матрице, чтобы все изменить. Я не уверен, что обязательно должна быть начальная позиция, возможно, она начинается с жестко закодированных значений. Затем, когда происходит событие прокрутки окна, мы каким-то образом выполняем некоторые вычисления, я не знаю, как это будет работать. Где я сейчас нахожусь, размышляя об этом, скажем, вы находитесь на Луне и прокручиваете вниз…. Мы каким-то образом заранее фиксируем высоту Луны статически, а затем говорим: «Луне потребуется 3000 пикселей, чтобы прокрутить ее» и тому подобное. Ну, «луна займет X количество высоты содержимого, чтобы прокрутить его». Таким образом, мы вычисляем высоту содержимого за пределами компонента SVG «сцена» и передаем в компонент SVG moonHeight / contentHeight initialOffset , и это положение прокрутки луны. Есть ли в этом какой-то смысл?

Затем мы так или иначе делаем это для каждого элемента. Но теперь мой разум начинает сходить с ума, пытаясь осмыслить, как все это легко/легко сочетается друг с другом. Когда луна прокручивается, вы начинаете видеть верхушки гор, и они тоже слегка прокручиваются. Так что это похоже на то, что каждый «слой» иллюстрации имеет прокручиваемый набор контрольных точек или что-то в этом роде. Область содержимого и область быстрого/неконтентного содержимого. Я не знаю, я теряюсь.

Любая помощь в обдумывании этого вопроса была бы весьма признательна. Я пока не знаю, куда с этим деваться. Как настроить исходную систему таким образом, чтобы она позволяла областям содержимого и областям, не связанным с контентом, перемещаться с разной скоростью и т. Д., Сохраняя при этом приблизительную общую компоновку сцены. Сколько для этого потребуется работы? Что я должен сделать, чтобы упростить?

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

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

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

Комментарии:

1. Я бы также пошел дальше и удалил все преобразования (думаю, вы используете Sketch), разделил слои на отдельные изображения, а затем добавил их в качестве фоновых изображений. Обновите положение фона для каждого изображения с помощью наблюдателя пересечений. Это может быть немного сложно понять, как реализовать, но это легко, как только вы заставите его работать. developer.mozilla.org/en-US/docs/Web/API/…

2. Полностью выполнимо, но как бы вы ни срезали его, будет утомительно адаптировать сцену SVG, которую вы должны использовать для комплексного эффекта, который вы описали, и заставить ее выглядеть правильно. Поскольку то, что вы описали, является таким специфическим эффектом, я не уверен, что существует «канонический» способ сделать это, однако я бы проверил этот метод, который использует 3D-преобразования CSS . Он достигает параллакса, в основном используя перспективу. Что касается ускорения и замедления в зависимости от положения прокрутки, для этого может потребоваться изменить координату Y перевода.

Ответ №1:

Если вы можете встроить svg в html и подготовить его с помощью групп, представляющих плоскости прокрутки параллакса, вы можете сделать что-то вроде приведенного ниже фрагмента.

Благодаря структуре svg эти группы уже расположены в порядке от начала до конца (от самого дальнего до ближайшего). Таким образом, вы можете вставить в атрибут id групп фактор параллакса, подобный prefixNN.NNN .

На стороне Javascript вам нужно только сопоставить группы, извлечь коэффициент параллакса, удалив префикс и проанализировав остальную часть значения в виде float.

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

Вот результат: https://jsfiddle.net/t50qo9cp/

Извините, я могу прикрепить только пример кода javascript из-за ограничений на количество символов.

 let svg = document.querySelector("svg");
let groups = document.querySelectorAll("svg g[id]");

let lastKnownScrollPosition = 0;
let ticking = false;

document.addEventListener('scroll', function(e) {
    lastKnownScrollPosition = window.scrollY;

    if (!ticking) {
        window.requestAnimationFrame(function() {
            parallax(lastKnownScrollPosition);
            ticking = false;
        });

        ticking = true;
    }
});

function parallax(scrollPos) {
    let delta = svg.clientTop   svg.clientHeight * 0.5 - scrollPos;

    for (let i = 0; i < groups.length;   i) {
        let id = groups[i].getAttribute("id");
        let factor = parseFloat(id.substr(1)) * delta;
        groups[i].setAttribute("transform", "translate(0 "   factor   ")");
    }
}
 

ОБНОВЛЕНИЕ: Более полная реализация

Я нашел способ обрабатывать также группы параллаксов, которые перпендикулярны плоскости обзора (т. Е. воде или реке на вашем изображении).

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

Вот рабочая демонстрация: https://jsfiddle.net/Lrqns1u9/

Все по-прежнему, за исключением:

  • код javascript для управления группами этого конкретного типа
  • новый способ указания коэффициента параллакса в идентификаторе в дальнем и ближнем порядке prefixNN.NNN-NN.NNN (см. Код ниже).
 let svg = document.querySelector("svg");
// Groups filtered by g prefix on id attribute.
let groups = document.querySelectorAll("svg g[id^='g']");
// Regex to match and extract parallax factor(s)
let groupRegex = /^w (d*(?:.d )?)(?:-(d*(?:.d )?))?$/;

let lastKnownScrollPosition = 0;
let ticking = false;

document.addEventListener('scroll', function(e) {
  lastKnownScrollPosition = window.scrollY;

  if (!ticking) {
    window.requestAnimationFrame(function() {
      parallax(lastKnownScrollPosition);
      ticking = false;
    });

    ticking = true;
  }
});

// Do parallax scrolling on groups.
function parallax(scrollPos) {
  let svgHeight = svg.getBBox().height;

  // This variable controls the vertical coordinate of the document in which
  // the image appears as visible in the editors (all groups have no transformation).
  let delta = svg.clientTop   svg.clientHeight * 0.5 - scrollPos;

  for (let i = 0; i < groups.length;   i) {
    let id = groups[i].getAttribute("id");
    let match = id.match(groupRegex);
    if (match === null)
      continue;

    let factor = parseFloat(match[1]) * delta;
    let transform = "translate(0 "   factor   ")";
    // If a second float is specified i.e.: 60.65-1.78 the group is perpendicular to
    // the viewing plane and need additional computation.
    if (match[2] != undefined) {
      let boundingBox = groups[i].getBBox();
      // Get parallax factor for bottom edge of group bounding box.
      let factorFront = parseFloat(match[2]) * delta   boundingBox.height;
      // Compute the scale for the group.
      let scale = (factorFront - factor) / boundingBox.height;
      // Compute the translation.
      let y = boundingBox.y   factor;

      // Tranform the group aligning first on zero y coordinate, then scale, 
      // then moving it back to correct position.
      transform = "translate(0, "   y   ") scale(1 "   scale   ") translate(0 "   -boundingBox.y   ")";
    }

    groups[i].setAttribute("transform", transform);
  }

}
 

Примечания:

  • svg должен быть правильно подготовлен, чтобы избежать наличия пустых областей в плоскостях параллакса
  • группы, помеченные как плоскости параллакса, не должны иметь преобразования, чтобы упростить обработку на стороне javascript.