Плавная прокрутка и смещение JavaScript scrollIntoView

У меня есть этот код для моего сайта:

function clickMe() {
  var element = document.getElementById('about');
  element.scrollIntoView({
    block: 'start',
    behavior: 'smooth',
  });
}

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

Есть ли способ иметь смещение и плавно прокручивать его?

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
56
0
51 530
9
Перейти к ответу Данный вопрос помечен как решенный

Ответы 9

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

Is there a way to have an offset and make it scroll smoothly?

Да, но не с scrollIntoView ()

scrollIntoViewOptions из Элемент.scrollIntoView () do нет позволяет вам использовать смещение. Это исключительно полезно, когда вы хотите прокрутить до позиции точный элемента.

Однако вы можете использовать Window.scrollTo () с опциями как для прокрутки до позиции компенсировать, так и для этого плавно.

Если у вас есть заголовок с высотой, например, 30px, вы можете сделать следующее:

function scrollToTargetAdjusted(){
    var element = document.getElementById('targetElement');
    var headerOffset = 45;
    var elementPosition = element.getBoundingClientRect().top;
    var offsetPosition = elementPosition - headerOffset;

    window.scrollTo({
         top: offsetPosition,
         behavior: "smooth"
    });
}

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

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

Посмотреть в действии

Вы можете сравнить оба варианта в приведенном ниже фрагменте.

<script type = "text/javascript">
  function scrollToTarget() {

    var element = document.getElementById('targetElement');
    element.scrollIntoView({
      block: "start",
      behavior: "smooth",
    });
  }

  function scrollToTargetAdjusted() {
    	var element = document.getElementById('targetElement');
      var headerOffset = 45;
    	var elementPosition = element.getBoundingClientRect().top;
      var offsetPosition = elementPosition - headerOffset;
      
      window.scrollTo({
          top: offsetPosition,
          behavior: "smooth"
      });   
  }

  function backToTop() {
    window.scrollTo(0, 0);
  }
</script>

<div id = "header" style = "height:30px; width:100%; position:fixed; background-color:lightblue; text-align:center;"> <b>Fixed Header</b></div>

<div id = "mainContent" style = "padding:30px 0px;">

  <button type = "button" onclick = "scrollToTarget();">element.scrollIntoView() smooth, header blocks view</button>
  <button type = "button" onclick = "scrollToTargetAdjusted();">window.scrollTo() smooth, with offset</button>

  <div style = "height:1000px;"></div>
  <div id = "targetElement" style = "background-color:red;">Target</div>
  <br/>
  <button type = "button" onclick = "backToTop();">Back to top</button>
  <div style = "height:1000px;"></div>
</div>
getBoundingClientRect().top относится к расстоянию между элементом и верхней частью области просмотра. Это более полезно, когда вы хотите использовать scrollBy, который прокручивается относительно текущей позиции, чем scrollTo, который является абсолютным. Если элемент не находится в относительно позиционируемом родителе, рассмотрите возможность использования вместо него offsetTop. В противном случае вам нужно добавить window.pageYOffset к getBoundingClientRect().top, чтобы получить правильное значение для scrollTo.
coreyward 14.02.2020 02:48

У меня это сработало очень хорошо, но для упрощения замените значение headerOffset (45) на document.getElementById ('заголовок'). offsetHeight. Это компенсирует любую отзывчивую высоту.

Suyog 18.08.2020 08:21

@coreyward +1 за подсказку window.pageYOffset

Anurag Srivastava 03.09.2020 09:45

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

Black 13.11.2020 17:04

Мой контейнер имеет фиксированную высоту 100vh, поэтому я не могу использовать прокрутку окна, и мне нужно использовать scrollIntoView, однако есть липкий заголовок, который нависает над элементом, в который он прокручивается. какой подход я могу использовать в этом случае?

Negin Basiri 19.03.2021 06:51

Ответ Сёрена Д. Птеуса направил меня на правильный путь, но у меня были проблемы с getBoundingClientRect(), когда он не был наверху window.

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

const element = document.getElementById('targetElement');
const offset = 45;
const bodyRect = document.body.getBoundingClientRect().top;
const elementRect = element.getBoundingClientRect().top;
const elementPosition = elementRect - bodyRect;
const offsetPosition = elementPosition - offset;

window.scrollTo({
  top: offsetPosition,
  behavior: 'smooth'
});

Пример Codepen

Не забудьте включить полифил при реализации этого!

Я надеюсь, что ваш const определен в пространстве имен функции, а не глобально, потому что он будет вычислять только расстояния относительно вашего окна просмотра при загрузке страницы.

PaulCo 10.12.2018 16:36

У меня сработало, спасибо. Была проблема с ответом Сорен Д. Птеус. Вы должны были быть на вершине, чтобы это работало.

Alexander Kim 03.03.2019 15:38

Главный ответ здесь

Julha 05.04.2019 17:29

Спасибо за это, у меня была такая же проблема, так как я использовал липкую панель навигации, это все исправило, еще раз спасибо!

JeFawk 11.03.2020 09:36

Это сработало для меня очень хорошо, спасибо

hamza saber 26.04.2021 18:51

Я пробовал другие решения, но у меня было странное поведение. Однако у меня это сработало.

function scrollTo(id) {
    var element = document.getElementById(id);
    var headerOffset = 60;
    var elementPosition = element.offsetTop;
    var offsetPosition = elementPosition - headerOffset;
    document.documentElement.scrollTop = offsetPosition;
    document.body.scrollTop = offsetPosition; // For Safari
}

и стиль:

html {
    scroll-behavior: smooth;
}

Простое, но элегантное решение если элемент имеет небольшую высоту (короче окна просмотра):

element.scrollIntoView({ behavior: 'instant', block: 'center' });

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

Лучший и простой ответ здесь

Jackal 10.09.2020 12:54

Прекрасная скрытая жемчужина :)

drooh 31.10.2020 21:01

поведение Необязательно Определяет анимацию перехода. Один из авто или плавный. По умолчанию авто. Откуда взялось «мгновение»?

drooh 01.11.2020 01:43

Просто и точно. Любить это :)

CodeIgnitor 25.11.2020 13:22

Ого, сенсация!

ikiK 05.02.2021 17:27

Это решило примерно 3-летние проблемы с обычным scrollIntoView. Спасибо!

Nils_e 03.05.2021 09:14

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

Скажем, ваш заголовок - 30px, и вам нужно смещение 15px, тогда:

  #about {
     padding-top: 45px; // this will allow you to scroll 15px below your 30px header
     margin-top: -45px; // and this will make sure that you don't change your layout because of it
  }

Хакер, но умно!

kirschkern 21.02.2021 21:23

Спасибо! Надеюсь, поможет!

Yulian 24.02.2021 10:31

Вот функция, которую я написал на основе Ответ @ekfuhrmann.
Он принимает элемент, который необходимо прокрутить, в качестве первого параметра, а другие параметры в форме объекта - в качестве второго параметра, аналогично тому, как работает функция window.scrollTo().

function scrollToTarget(element, options) {
    if (options.headerHeight === undefined) {
        options.headerHeight = 0;
    }

    var elementRect = element.getBoundingClientRect();

    // If an element has 0 height, then it is hidden, do not scroll
    if (elementRect.height == 0) {
        return;
    }

    var offset = elementRect.top - options.headerHeight;

    if (options.block == 'center') {
        // If an element's height is smaller, than the available screen height (without the height of the header), then add the half of the available space
        // to scroll to the center of the screen
        var availableSpace = window.innerHeight - options.headerHeight;
        if (elementRect.height < availableSpace) {
            offset -= (availableSpace - elementRect.height) / 2;
        }
    }

    var optionsToPass = {
        top: offset
    };
    if (options.behavior !== undefined) {
        optionsToPass.behavior = options.behavior
    }

    window.scrollBy(optionsToPass);
}

Основное отличие состоит в том, что он использует функцию window.scrollBy() вместо window.scrollTo(), поэтому нам не нужно вызывать .getBoundingClientRect() на body.

Параметр options может содержать поле headerHeight - он может содержать высоту фиксированного элемента на экране, который необходимо игнорировать при прокрутке к элементу.

Эта функция также может иметь опцию block, которая пока может принимать только одно значение "center". Если установлено, элемент, к которому выполняется прокрутка, будет отображаться в центре экрана, за исключением фиксированной высоты элемента. По умолчанию прокрутка будет применена к верхней части элемента.

Пример использования

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

В следующем примере показано, что элемент появится в центре доступной высоты области просмотра, если для параметра block установлено значение "center", аналогично тому, как работает функция Element.scrollIntoView().

function scrollToTarget(element, options) {
    if (options.headerHeight === undefined) {
        options.headerHeight = 0;
    }

    var elementRect = element.getBoundingClientRect();

    if (elementRect.height == 0) {
        return;
    }

    var offset = elementRect.top - options.headerHeight;

    if (options.block == 'center') {
        var availableSpace = window.innerHeight - options.headerHeight;
        if (elementRect.height < availableSpace) {
            offset -= (availableSpace - elementRect.height) / 2;
        }
    }

    var optionsToPass = {
        top: offset
    };
    if (options.behavior !== undefined) {
        optionsToPass.behavior = options.behavior
    }

    window.scrollBy(optionsToPass);
}

var headerElements  = [
  document.querySelector('.header__wrap'),
  document.getElementById('wpadminbar')
];
var maxHeaderHeight = headerElements.reduce(function (max, item) {
  return item ? Math.max(max, item.offsetHeight) : max;
}, 0);

document.getElementById('click-me').addEventListener('click', function() {
  scrollToTarget(document.querySelector('.scroll-element'), {
    headerHeight: maxHeaderHeight,
    block: 'center',
    behavior: 'smooth'
  });
});
body {
  margin: 0;
  height: 1000px;
}
#wpadminbar, .header__wrap {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
}
#wpadminbar {
  height: 32px;
  background-color: #1d2327;
  z-index: 2;
  opacity: 0.8;
}
.header__wrap {
  margin: 0 15px;
  height: 74px;
  background-color: #436c50;
  z-index: 1;
}
.scroll-element {
  margin-top: 500px;
  padding: 1em;
  text-align: center;
  background-color: #d7d7d7;
}
#click-me {
  margin: 100px auto 0;
  padding: 0.5em 1em;
  display: block;
}
<div id = "wpadminbar"></div>
<div class = "header__wrap"></div>
<button id = "click-me">Click me!</button>
<!-- Some deeply nested HTML element -->
<div class = "scroll-element">
  You scrolled to me and now I am in the visual center of the screen. Nice!
</div>

Также есть поля прокрутки и прокрутки.

Для меня прокрутка наиболее полезна для такого рода вещей.

/* Keyword values */
scroll-padding-top: auto;

/* <length> values */
scroll-padding-top: 10px;
scroll-padding-top: 1em;
scroll-padding-top: 10%;

/* Global values */
scroll-padding-top: inherit;
scroll-padding-top: initial;
scroll-padding-top: unset;

Однако, скорее всего, он несовместим с Internet Explorer.

Только что видел это в новом видео Fireship! Для плавной прокрутки используйте scroll-behavior: smooth;

Tim Wijma 14.04.2021 23:29

Сорен Д. Птеус почти прав, но он работает только тогда, когда пользователь находится сверху. Это потому, что getBoundingClientRect всегда будет получать относительную высоту, а использование window.scrollTo с относительной высотой не работает.

Ekfuhrmann улучшил ответ, получив общую высоту от элемента тела и вычислив реальную высоту. Однако я думаю, что это может быть проще, мы можем просто использовать относительное положение и использовать window.scrollBy.

Примечание. Основное отличие - window.scrollBy.

const HEADER_HEIGHT = 45;

function scrollToTargetAdjusted(){
    const element = document.getElementById('targetElement');
    const elementPosition = element.getBoundingClientRect().top;
    const offsetPosition = elementPosition - HEADER_HEIGHT;

    window.scrollBy({
         top: offsetPosition,
         behavior: "smooth"
    });
}

Спасибо. Это должен быть принятый ответ.

laszlo 31.05.2021 19:28

Собственно, это должен быть принятый.

saibbyweb 05.06.2021 23:51

С помощью очень небольшого взлома вы можете заставить его работать с scrollIntoView ()

  • Допустим, вы хотите перейти к разделу, и ваши элементы имеют следующий формат:
<section id = "about">
 <p>About title</p>
 <p>About description</p>
</section>

<section id = "profile">
 <p>About title</p>
 <p>About description</p>
</section>
  • Вы конвертируете приведенный выше код в этот:
<section>
 <span className = "section-offset" id = "about"></span>
 <!-- or <span className = "section-offset" id = "about" />  for React -->
 <p>About title</p>
 <p>About description</p>
</section>

<section>
 <span className = "section-offset" id = "profile"></span>
 <p>Profile title</p>
 <p>Profile description</p>
</section>
  • Затем в вашем css вы можете легко изменить смещение, используя:
.section-offset {
  position: relative;
  bottom: 60px; // <<< your offset here >>>
}

Заключение:

Переместите селектор элемента в диапазон внутри раздела, затем вы можете использовать position: relative в диапазоне (размещение сверху / снизу не влияет на другие элементы на странице), чтобы установить необходимое смещение. Если вам нужно смещение снизу, поместите элемент span в конце раздела (например, перед </section>).

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