scrollIntoView Прокручивает слишком далеко

113

У меня есть страница, на которой полоса прокрутки, содержащая строки таблицы с div в них, динамически создается из базы данных. Каждая строка таблицы действует как ссылка, вроде того, что вы видите в плейлисте YouTube рядом с видеоплеером.

Когда пользователь посещает страницу, опция, на которой он находится, должна находиться в верхней части прокручиваемого div. Этот функционал работает. Проблема в том, что это заходит слишком далеко. Как и вариант, который они используют, на 10 пикселей больше. Итак, страница посещается, URL-адрес используется для определения того, какой вариант был выбран, а затем прокручивается этот параметр в верхнюю часть прокручиваемого div. Примечание. Это не полоса прокрутки окна, это div с полосой прокрутки.

Я использую этот код, чтобы переместить выбранный параметр в верхнюю часть div:

var pathArray = window.location.pathname.split( '/' );

var el = document.getElementById(pathArray[5]);

el.scrollIntoView(true);

Он перемещает его в верхнюю часть div, но примерно на 10 пикселей выше. Кто-нибудь знает, как это исправить?

Мэтью Уилсон
источник
47
Беспокоит отсутствие конфигурации смещения для scrollIntoView.
mystrdat

Ответы:

59

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

el.scrollIntoView(true);
document.getElementById("containingDiv").scrollTop -= 10;
Лукас Тшесневски
источник
2
Будет ли это иметь такой же анимационный эффект, как scrollIntoView?
1252748
1
@thomas AFAIK, простая scrollIntoViewфункция DOM не вызывает анимацию. Вы говорите о плагине scrollIntoView jQuery?
Lucas Trzesniewski
1
Это сработало, но это было неправильное направление, поэтому все, что мне нужно было сделать, это изменить его с + = 10 на - = 10, и теперь он загружается правильно, большое спасибо за помощь !!!!
Мэтью Уилсон
Ага. Я обескуражен. Я думал, это анимировано. Сожалею!
1252748
17
Его можно оживить, просто добавь el.scrollIntoView({ behavior: 'smooth' });. Однако поддержка невелика!
daveaspinall
115

Плавно прокрутите до нужной позиции

Получите правильную y координату и используйтеwindow.scrollTo({top: y, behavior: 'smooth'})

const id = 'profilePhoto';
const yOffset = -10; 
const element = document.getElementById(id);
const y = element.getBoundingClientRect().top + window.pageYOffset + yOffset;

window.scrollTo({top: y, behavior: 'smooth'});
Арсений-II
источник
2
Это наиболее правильный ответ - мы можем легко найти положение прокрутки с помощью, jQuery('#el').offset().topа затем использоватьwindow.scrollTo
Александр Гончаров
1
+1 за это решение. Это помогло мне правильно прокрутить до элемента, когда у меня был контейнер, расположенный относительно с верхним смещением.
Liga
Любой, кто пришел сюда, используя HashLink с React Router, это то, что сработало для меня. В прокрутки пропеллер на вашем HashLink, scroll={el => { const yCoordinate = el.getBoundingClientRect().top + window.pageYOffset; const yOffset = -80; window.scrollTo({ top: yCoordinate + yOffset, behavior: 'smooth' }); }}- где смещение составляет 10-15 пикселов больше , чем размер вашего заголовка только для лучшего расстояния
Rob B
86

Сделать это можно в два этапа:

el.scrollIntoView(true);
window.scrollBy(0, -10); // Adjust scrolling with a negative value here
fred727
источник
23
Если перед scrollIntoView()запуском прокрутка не завершилась scrollBy(), последняя прокрутка отменит первую. По крайней мере, в Chrome 71.
Дэйв Хьюз
1
В этом случае используйте setTimeout, как показано в ответе ниже: stackoverflow.com/a/51935129/1856258 .. Для меня это работает без тайм-аута. Спасибо!
leole
Есть ли способ напрямую ввести корректор пикселей в scrollIntoView, чтобы приложение переместилось прямо в соответствующее место? Типа scrollIntoView({adjustment:y}), я думаю, это должно быть возможно с помощью специального кода в конце
Webwoman
80

Позиционирование якоря абсолютным методом

Другой способ сделать это - расположить якоря именно там, где вы хотите, на странице, а не полагаться на прокрутку по смещению. Я считаю, что это позволяет лучше контролировать каждый элемент (например, если вам нужно другое смещение для определенных элементов), а также может быть более устойчивым к изменениям / различиям API браузера.

<div id="title-element" style="position: relative;">
  <div id="anchor-name" style="position: absolute; top: -100px; left: 0"></div>
</div>

Теперь смещение указано как -100 пикселей относительно элемента. Создайте функцию для создания этой привязки для повторного использования кода, или, если вы используете современную структуру JS, такую ​​как React, сделайте это, создав компонент, который отображает вашу привязку, и передайте имя привязки и выравнивание для каждого элемента, который может или может не быть таким же.

Тогда просто используйте:

const element = document.getElementById('anchor-name')
element.scrollIntoView({ behavior: 'smooth', block: 'start' });

Для плавной прокрутки со смещением 100 пикселей.

Стив Бэнтон
источник
12
Это, безусловно, лучший способ реализовать это плавно и просто.
catch22
2
Я считаю, что это лучший и самый родной метод.
Даниил Пронин
1
Это так просто и красиво. Многие другие просто добавляют ненужный JS.
RedSands
2
Это особенно полезно, когда ссылка типа «вверху страницы» находится под панелью навигации, и вы хотите показать навигацию, но не хотите смещения других ссылок
Джо Мун,
вы можете добиться того же, используя отрицательный маржинальный верх, вы избегаете обертывания элемента вместе с другим с относительной позицией
Серги
19

Я решил эту проблему, используя,

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

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

Надеюсь, поможет...

Имтиаз Шакил Сиддик
источник
1
Надо использовать scrollToне на, elementа наwindow
Арсений-II
@ Arseniy-II Спасибо, что указали на это !!. Я пропустил эту часть!
Имтиаз Шакил Сиддик
блок помогает наверху?
Ашок Кумар Ганешан
13

Это работает для меня в Chrome (с плавной прокруткой и без временных интервалов)

Он просто перемещает элемент, запускает прокрутку, а затем перемещает его назад.

Если элемент уже находится на экране, видимого "выскакивания" не происходит.

pos = targetEle.style.position;
top = targetEle.style.top;
targetEle.style.position = 'relative';
targetEle.style.top = '-20px';
targetEle.scrollIntoView({behavior: 'smooth', block: 'start'});
targetEle.style.top = top;
targetEle.style.position = pos;
Райан Хау
источник
Это лучшее решение ... Хотелось бы, чтобы оно было отмечено как ответ. Сэкономил бы пару часов.
Шивам
Протестировал и работает из коробки. Не хакер и отлично работает! Проголосуйте за это!
Йенс Торнелл
7

Основываясь на предыдущем ответе, я делаю это в проекте Angular5.

Началось с:

// el.scrollIntoView(true);
el.scrollIntoView({
   behavior: 'smooth',
   block: 'start'
});
window.scrollBy(0, -10); 

Но это дало некоторые проблемы и нужно было установить время для scrollBy () следующим образом:

//window.scrollBy(0,-10);
setTimeout(() => {
  window.scrollBy(0,-10)
  }, 500);

И он отлично работает в MSIE11 и Chrome 68+. В FF не тестировал. 500 мс были самой короткой задержкой, на которую я бы рискнул. Спуск вниз иногда не удался, поскольку плавная прокрутка еще не завершилась. Отрегулируйте, как требуется для вашего собственного проекта.

+1 к Fred727 за это простое, но эффективное решение.

PrimaryW
источник
6
Немного неудобно плавно переходить к элементу, а затем немного подниматься вверх.
catch22
6

Предполагая, что вы хотите прокрутить до div, которые находятся на одном уровне в DOM и имеют имя класса «scroll-with-offset», тогда этот CSS решит проблему:

.scroll-with-offset {    
  padding-top: 100px;
  margin-bottom: -100px;
}

Смещение от верха страницы составляет 100 пикселей. Он будет работать только по назначению с block: 'start':

element.scrollIntoView({ behavior: 'smooth', block: 'start' });

Что происходит, так это то, что верхняя точка div находится в обычном месте, но их внутреннее содержимое начинается на 100 пикселей ниже нормального местоположения. Вот для чего нужен padding-top: 100px. margin-bottom: -100px - это смещение дополнительного поля нижнего div. Чтобы завершить решение, также добавьте этот CSS для смещения полей / отступов для самого верхнего и самого нижнего div:

.top-div {
  padding-top: 0;
}
.bottom-div {
  margin-bottom: 0;
}
Георгий Петухов
источник
6

Это решение принадлежит @ Arseniy-II , я просто упростил его до функции.

function _scrollTo(selector, yOffset = 0){
  const el = document.querySelector(selector);
  const y = el.getBoundingClientRect().top + window.pageYOffset + yOffset;

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

Использование (вы можете открыть консоль прямо здесь, в StackOverflow и протестировать ее):

_scrollTo('#question-header', 0);
Диего Фортес
источник
4

Вы также можете использовать element.scrollIntoView() опции

el.scrollIntoView(
  { 
    behavior: 'smooth', 
    block: 'start' 
  },
);

который поддерживает большинство браузеров

Николас Мюррей
источник
34
Однако это не поддерживает какой-либо вариант смещения.
adriendenat
3

У меня есть это, и он отлично работает для меня:

// add a smooth scroll to element
scroll(el) {
el.scrollIntoView({
  behavior: 'smooth',
  block: 'start'});

setTimeout(() => {
window.scrollBy(0, -40);
}, 500);}

Надеюсь, поможет.

Фернандо Брандао
источник
2

Другое решение - использовать offsetTop, например:

var elementPosition = document.getElementById('id').offsetTop;

window.scrollTo({
  top: elementPosition - 10, //add your necessary value
  behavior: "smooth"  //Smooth transition to roll
});
Кайо Дутра
источник
2

Так что, возможно, это немного неуклюже, но пока все хорошо. Я работаю в angular 9.

файл .ts

scroll(el: HTMLElement) {
  el.scrollIntoView({ block: 'start',  behavior: 'smooth' });   
}

файл .html

<button (click)="scroll(target)"></button>
<div  #target style="margin-top:-50px;padding-top: 50px;" ></div>

Я регулирую смещение с помощью полей и отступов сверху.

Салудо!

Игнасио Басти
источник
1

Нашел обходное решение. Скажем, вы хотите прокрутить до div, например, Element, и хотите, чтобы над ним был интервал 20 пикселей. Установите ссылку на созданный над ним div:

<div ref={yourRef} style={{position: 'relative', bottom: 20}}/> <Element />

Это создаст желаемый интервал.

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

Ибрагим Аль Масри
источник
0

Моя основная идея - создать tempDiv над представлением, к которому мы хотим прокрутить. Он хорошо работает без запаздывания в моем проекте.

scrollToView = (element, offset) => {
    var rect = element.getBoundingClientRect();
    var targetY = rect.y + window.scrollY - offset;

    var tempDiv;
    tempDiv = document.getElementById("tempDiv");
    if (tempDiv) {
        tempDiv.style.top = targetY + "px";
    } else {
        tempDiv = document.createElement('div');
        tempDiv.id = "tempDiv";
        tempDiv.style.background = "#F00";
        tempDiv.style.width = "10px";
        tempDiv.style.height = "10px";
        tempDiv.style.position = "absolute";
        tempDiv.style.top = targetY + "px";
        document.body.appendChild(tempDiv);
    }

    tempDiv.scrollIntoView({ behavior: 'smooth', block: 'start' });
}

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

onContactUsClick = () => {
    this.scrollToView(document.getElementById("contact-us"), 48);
}

Надеюсь, это поможет

Фан Ван Линь
источник
0

Основываясь на ответе Арсения-II: у меня был вариант использования, в котором объект прокрутки был не самим окном, а внутренним шаблоном (в данном случае div). В этом сценарии нам нужно установить идентификатор для контейнера прокрутки и получить его, getElementByIdчтобы использовать его функцию прокрутки:

<div class="scroll-container" id="app-content">
  ...
</div>
const yOffsetForScroll = -100
const y = document.getElementById(this.idToScroll).getBoundingClientRect().top;
const main = document.getElementById('app-content');
main.scrollTo({
    top: y + main.scrollTop + yOffsetForScroll,
    behavior: 'smooth'
  });

Оставьте его здесь на случай, если кто-то столкнется с подобной ситуацией!

Kounex
источник
0

Вот мои 2 цента.

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

HTML:

//anchor tag that appears multiple times on the page
<a href="#" class="anchors__link js-anchor" data-target="schedule">
    <div class="anchors__text">
        Scroll to the schedule
    </div>
</a>

//The node we want to scroll to, somewhere on the page
<div id="schedule">
    //html
</div>

Файл Javascript:

(() => {
    'use strict';

    const anchors = document.querySelectorAll('.js-anchor');

    //if there are no anchors found, don't run the script
    if (!anchors || anchors.length <= 0) return;

    anchors.forEach(anchor => {
        //get the target from the data attribute
        const target = anchor.dataset.target;

        //search for the destination element to scroll to
        const destination = document.querySelector(`#${target}`);
        //if the destination element does not exist, don't run the rest of the code
        if (!destination) return;

        anchor.addEventListener('click', (e) => {
            e.preventDefault();
            //create a new element and add the `anchors__generated` class to it
            const generatedAnchor = document.createElement('div');
            generatedAnchor.classList.add('anchors__generated');

            //get the first child of the destination element, insert the generated element before it. (so the scrollIntoView function scrolls to the top of the element instead of the bottom)
            const firstChild = destination.firstChild;
            destination.insertBefore(generatedAnchor, firstChild);

            //finally fire the scrollIntoView function and make it animate "smoothly"
            generatedAnchor.scrollIntoView({
                behavior: "smooth",
                block: "start",
                inline: "start"
            });

            //remove the generated element after 1ms. We need the timeout so the scrollIntoView function has something to scroll to.
            setTimeout(() => {
                destination.removeChild(generatedAnchor);
            }, 1);
        })
    })
})();

CSS:

.anchors__generated {
    position: relative;
    top: -100px;
}

Надеюсь, это кому-нибудь поможет!

рубеус
источник
0

Я добавляю эти советы css для тех, кто не решил эту проблему с помощью решений выше:

#myDiv::before {
  display: block;
  content: " ";
  margin-top: -90px; // adjust this with your header height
  height: 90px; // adjust this with your header height
  visibility: hidden;
}
Жереми МАРКЕР
источник
0

UPD: я создал пакет npm, который работает лучше, чем следующее решение, и более прост в использовании.

Моя функция smoothScroll

Я взял замечательное решение Стива Бентона и написал функцию, которая делает его более удобным в использовании. Было бы проще использовать window.scroll()или даже window.scrollBy(), как я пробовал раньше, но у этих двоих есть некоторые проблемы:

  • Все становится бесполезным после их использования с плавным поведением.
  • Вы ни в коем случае не можете предотвратить их, и вам придется подождать до и свитка. Надеюсь, моя функция будет вам полезна. Также есть легкий полифилл, который позволяет ему работать в Safari и даже в IE.

Вот код

Просто скопируйте это и возитесь с этим, как хотите.

import smoothscroll from 'smoothscroll-polyfill';

smoothscroll.polyfill();

const prepareSmoothScroll = linkEl => {
  const EXTRA_OFFSET = 0;

  const destinationEl = document.getElementById(linkEl.dataset.smoothScrollTo);
  const blockOption = linkEl.dataset.smoothScrollBlock || 'start';

  if ((blockOption === 'start' || blockOption === 'end') && EXTRA_OFFSET) {
    const anchorEl = document.createElement('div');

    destinationEl.setAttribute('style', 'position: relative;');
    anchorEl.setAttribute('style', `position: absolute; top: -${EXTRA_OFFSET}px; left: 0;`);

    destinationEl.appendChild(anchorEl);

    linkEl.addEventListener('click', () => {
      anchorEl.scrollIntoView({
        block: blockOption,
        behavior: 'smooth',
      });
    });
  }

  if (blockOption === 'center' || !EXTRA_OFFSET) {
    linkEl.addEventListener('click', () => {
      destinationEl.scrollIntoView({
        block: blockOption,
        behavior: 'smooth',
      });
    });
  }
};

export const activateSmoothScroll = () => {
  const linkEls = [...document.querySelectorAll('[data-smooth-scroll-to]')];

  linkEls.forEach(linkEl => prepareSmoothScroll(linkEl));
};

Чтобы создать элемент ссылки, просто добавьте следующий атрибут данных:

data-smooth-scroll-to="element-id"

Также вы можете установить другой атрибут в качестве дополнения

data-smooth-scroll-block="center"

Он представляет собой blockвариант scrollIntoView()функции. По умолчанию это start. Узнать больше о MDN .

в заключение

Настройте функцию smoothScroll в соответствии с вашими потребностями.

Например, если у вас есть фиксированный заголовок (или я называю его словом masthead), вы можете сделать что-то вроде этого:

const mastheadEl = document.querySelector(someMastheadSelector);

// and add it's height to the EXTRA_OFFSET variable

const EXTRA_OFFSET = mastheadEl.offsetHeight - 3;

Если у вас нет такого дела, то просто удалите его, почему бы и нет :-D.

Дмитрий Захаров
источник
-1

Простое решение для прокрутки определенного элемента вниз

const element = document.getElementById("element-with-scroll");
element.scrollTop = element.scrollHeight - 10;
Хури
источник
-1

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

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

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

<html>
<head>
    <title>Scrolling Into View</title>
    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
    <style>
        div.scroll { height: 6em; width: 20em; overflow: auto; }
        thead th   { position: sticky; top: -1px; background: #fff; }
        .up, .down { cursor: pointer; }
        .up:hover, .down:hover { color: blue; text-decoration:underline; }
    </style>
</head>
<body>
<div class='scroll'>
<table border='1'>
    <thead>
        <tr>
            <th>Review</th>
            <th>Data</th>
        </tr>
    </thead>
    <tbody>
        <tr id='row_1'>
            <th></th>
            <td>Row 1 (OK)</td>
        </tr>
        <tr id='row_2'>
            <th></th>
            <td>Row 2 (OK)</td>
        </tr>
        <tr id='row_3'>
            <th id='jump_1'><span class='up'>UP</span> <span class='down'>DN</span></th>
            <td>Row 3 (REVIEW)</td>
        </tr>
        <tr id='row_4'>
            <th></th>
            <td>Row 4 (OK)</td>
        </tr>
        <tr id='row_5'>
            <th id='jump_2'><span class='up'>UP</span> <span class='down'>DN</span></th>
            <td>Row 5 (REVIEW)</td>
        </tr>
        <tr id='row_6'>
            <th></th>
            <td>Row 6 (OK)</td>
        </tr>
        <tr id='row_7'>
            <th></th>
            <td>Row 7 (OK)</td>
        </tr>
        <tr id='row_8'>
            <th id='jump_3'><span class='up'>UP</span> <span class='down'>DN</span></th>
            <td>Row 8 (REVIEW)</td>
        </tr>
        <tr id='row_9'>
            <th></th>
            <td>Row 9 (OK)</td>
        </tr>
        <tr id='row_10'>
            <th></th>
            <td>Row 10 (OK)</td>
        </tr>
    </tbody>
</table>
</div>
<script>
$(document).ready( function() {
    $('.up').on('click', function() {
        var id = parseInt($(this).parent().attr('id').split('_')[1]);
        if (id>1) {
            var row_id = $('#jump_' + (id - 1)).parent().attr('id').split('_')[1];
            document.getElementById('row_' + (row_id-1)).scrollIntoView({behavior: 'smooth', block: 'start'});
        } else {
            alert('At first');
        }
    });

    $('.down').on('click', function() {
        var id = parseInt($(this).parent().attr('id').split('_')[1]);
        if ($('#jump_' + (id + 1)).length) {
            var row_id = $('#jump_' + (id + 1)).parent().attr('id').split('_')[1];
            document.getElementById('row_' + (row_id-1)).scrollIntoView({behavior: 'smooth', block: 'start'});
        } else {
            alert('At last');
        }
    });
});
</script>
</body>
</html>
Мартин Фрэнсис
источник