Сохраняйте позицию прокрутки при добавлении элемента вверху - PullRequest
4 голосов
/ 10 июля 2020

Я пытаюсь создать календарь, который можно бесконечно прокручивать влево и вправо. Он должен динамически загружать новый контент, когда пользователь прокручивает вперед (легко) или назад (проблема здесь).

Когда я добавляю контент в конец страницы, он работает нормально - полоса прокрутки настраивается на новый container.scrollWidth. Но когда мне нужно добавить контент в начало, весь календарь перемещается вправо огромным скачком в 400 пикселей, потому что свойство container.scrollLeft не изменилось, и теперь в начале есть новый элемент.

Я пытаюсь смягчить это, увеличивая container.scrollLeft на 400 пикселей - ширину вновь созданного элемента. Этот подход работает, но только при прокрутке с помощью колесика мыши (shift + колесо мыши для прокрутки в сторону) или мобильного сенсорного экрана.

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

Не могли бы вы предложить способ достижения этого поведения для всех способов прокрутки?

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

Вот мой полуработающий пример:

function Container() {
  const rectangleWidth = 400;

  const container = React.useRef(null);

  const [leftRectangles, setLeftRectangles] = React.useState([0]);
  const [rightRectangles, setRightRectangles] = React.useState([1, 2, 3, 4]);


    // When we just rendered a new rectangle in the left of our container,
  // move the scroll position back
  React.useEffect(() => {
    container.current.scrollLeft += rectangleWidth;
  }, [leftRectangles]);

  const loadLeftRectangle = () => {
    const newAddress = leftRectangles[0] - 1;
    setLeftRectangles([newAddress, ...leftRectangles]);
  };

  const loadRightRectangle = () => {
    const newAddress = rightRectangles[rightRectangles.length - 1] + 1;
    setRightRectangles([...rightRectangles, newAddress]);
  };

  const handleScroll = (e) => {
    // When there is only 100px of content left, load new content
    const loadingOffset = 100;
    
    if (e.target.scrollLeft < loadingOffset) {
      loadLeftRectangle(e.target);
    } else if (e.target.scrollLeft > e.target.scrollWidth - e.target.clientWidth - loadingOffset) {
      loadRightRectangle(e.target);
    }
  };

  return (
    <div
      className="container"
      onScroll={handleScroll}
      ref={container}
    >
      {leftRectangles.map((address) => (
        <div className="rectangle" key={address}>
          {address}
        </div>
      ))}
      {rightRectangles.map((address) => (
        <div className="rectangle" key={address}>
          {address}
        </div>
      ))}
    </div>
  );
}

ReactDOM.render(<Container />, document.querySelector("#app"))
.container {
  display: flex;
  overflow-x: scroll;
}

.rectangle {
  border: 1px solid #000;
  box-sizing: border-box;
  flex-shrink: 0;
  height: 165px;
  width: 400px;
  font-size: 50px;
  line-height: 165px;
  text-align: center;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>

Ответы [ 2 ]

2 голосов
/ 13 июля 2020

Я думаю, это тот случай, когда не следует использовать собственные области прокрутки браузера. Если задуматься, полосы прокрутки не имеют значения, если они продолжают бесконечно увеличиваться в обоих направлениях. Или их единственное значение - «просматриваемая область». Полоса прокрутки не является хорошей метафорой для бесконечной области, и поэтому полоса прокрутки - плохой способ делать то, что вы хотите здесь.

Если вы думаете об этом иначе, вы показываете только набор месяцев, которые соответствуют известной ширине экрана. Я бы подошел к этому так, чтобы каждый календарь находился в абсолютном положении внутри контейнера и вычислял их позиции в рендере l oop в зависимости от того, где в виртуальном представлении перемещается пользователь. Это также позволит вам удалять календари, когда они go слишком далеко от экрана, и создавать / буферизовать их за пределами экрана для отображения до того, как пользователь переместится в их положение. Это помешало бы вам иметь объект произвольной ширины, который в конечном итоге замедлил бы рендеринг. линия. Месяц для x должен иметь позицию viewX - x. Когда пользователь перетаскивает, вы перемещаете viewX обратно и перемещаете буферизованные элементы. Когда пользователь прекращает перетаскивание, вы берете последнюю скорость и замедляете ее, пока viewX - x не станет целым числом.

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

1 голос
/ 12 июля 2020

Используйте read-write scrollLeft, чтобы получить позицию прокрутки перед динамическим добавлением содержимого, затем используйте метод .scrollLeft, чтобы переместить позицию прокрутки обратно туда, где вы хотите. Возможно, вам потребуется запустить диалоговое окно , отображающее неопределенный индикатор выполнения (или просто отобразить текст «работает ...»), который отображается во время процесса, чтобы предотвратить дергание .

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

Еще одна особенность, которую следует учитывать в отношении задержки, - это CSS переходы , при которых добавление контента (например, элемента блока) будет постепенно исчезать / анимировать в окне просмотра.

...