scrollIntoView Cuộn quá xa


106

Tôi có một trang trong đó thanh cuộn chứa các hàng trong bảng có div trong đó được tạo động từ cơ sở dữ liệu. Mỗi hàng trong bảng hoạt động giống như một liên kết, giống như bạn thấy trên danh sách phát YouTube bên cạnh trình phát video.

Khi người dùng truy cập trang, tùy chọn mà họ đang sử dụng được cho là chuyển đến đầu div cuộn. Chức năng này đang hoạt động. Vấn đề là nó đã đi quá xa. Giống như tùy chọn mà họ đang bật quá cao khoảng 10px. Vì vậy, trang được truy cập, url được sử dụng để xác định tùy chọn nào đã được chọn và sau đó cuộn tùy chọn đó lên đầu div cuộn. Lưu ý: Đây không phải là thanh cuộn cho cửa sổ, nó là một div với một thanh cuộn.

Tôi đang sử dụng mã này để làm cho nó di chuyển tùy chọn đã chọn lên đầu div:

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

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

el.scrollIntoView(true);

Nó di chuyển nó lên đầu div nhưng quá xa khoảng 10 pixel. Bất cứ ai cũng biết làm thế nào để khắc phục điều đó?


44
Việc thiếu cấu hình bù đắp scrollIntoViewđang gây rắc rối.
mystrdat,

Câu trả lời:


56

Nếu nó khoảng 10px, thì tôi đoán bạn có thể chỉ cần điều chỉnh thủ công độ divlệch cuộn của chứa như thế:

el.scrollIntoView(true);
document.getElementById("containingDiv").scrollTop -= 10;

1
Điều này sẽ có hiệu ứng hoạt hình giống như scrollIntoView?
1252748

1
@thomas AFAIK, scrollIntoViewhàm DOM thuần túy không gây ra hoạt ảnh. Bạn đang nói về plugin scrollIntoView jQuery?
Lucas Trzesniewski

1
Người đàn ông đó đã làm việc, ngoại trừ đó là hướng sai vì vậy tất cả những gì tôi phải làm là thay đổi nó từ + = 10 thành - = 10 và bây giờ nó đang tải vừa phải, cảm ơn rất nhiều vì đã giúp đỡ !!!!
Matthew Wilson

Vâng. Tôi đã nhầm lẫn. Tôi nghĩ nó hoạt hình. Lấy làm tiếc!
1252748

16
Nó có thể hoạt hình, chỉ cần thêm el.scrollIntoView({ behavior: 'smooth' });. Hỗ trợ không phải là tuyệt vời mặc dù!
daveaspinall

108

Cuộn mượt mà đến vị trí thích hợp

Nhận tọa độ chính xác y và sử dụngwindow.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'});

2
Đây là câu trả lời chính xác nhất - chúng tôi easly có thể tìm thấy vị trí của cuộn với jQuery('#el').offset().topvà sau đó sử dụngwindow.scrollTo
Alexander Goncharov

1
+1 cho giải pháp này. Nó giúp tôi cuộn đến phần tử một cách chính xác khi tôi có một vùng chứa được định vị tương đối với độ lệch trên cùng.
Liga

Bất cứ ai đến đây sử dụng HashLink với Bộ định tuyến React, đây là những gì đã làm việc cho tôi. Trong phần hỗ trợ cuộn trên HashLink của bạn, scroll={el => { const yCoordinate = el.getBoundingClientRect().top + window.pageYOffset; const yOffset = -80; window.scrollTo({ top: yCoordinate + yOffset, behavior: 'smooth' }); }}- nơi bù đắp nhiều hơn 10-15 pixel so với kích thước tiêu đề của bạn chỉ để có khoảng cách tốt hơn
Rob B

86

Bạn có thể thực hiện theo hai bước:

el.scrollIntoView(true);
window.scrollBy(0, -10); // Adjust scrolling with a negative value here

22
Nếu scrollIntoView()cuộn chưa hoàn thành trước khi scrollBy()chạy, cuộn sau sẽ hủy cuộn trước. Ít nhất là trên Chrome 71.
Dave Hughes

1
Trong trường hợp này, hãy sử dụng setTimeout như được thấy trong câu trả lời bên dưới: stackoverflow.com/a/51935129/1856258 .. Đối với tôi, nó hoạt động mà không có Timeout. Cảm ơn!
leole 26/02/19

có cách nào để đưa trực tiếp trình sửa lỗi của pixel vào scrollIntoView để làm cho ứng dụng di chuyển trực tiếp đến nơi có liên quan không? Giống như scrollIntoView({adjustment:y}), tôi nghĩ rằng nó sẽ có thể với mã tùy chỉnh ở cuối
Webwoman

78

Định vị neo theo phương pháp tuyệt đối

Một cách khác để làm điều này là định vị các neo của bạn chính xác ở vị trí bạn muốn trên trang thay vì dựa vào việc cuộn theo bù đắp. Tôi thấy nó cho phép kiểm soát tốt hơn đối với từng phần tử (ví dụ: nếu bạn muốn một khoảng bù khác cho các phần tử nhất định) và cũng có thể chống lại các thay đổi / khác biệt API của trình duyệt.

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

Bây giờ độ lệch được chỉ định là -100px so với phần tử. Tạo một hàm để tạo liên kết này để sử dụng lại mã hoặc nếu bạn đang sử dụng khung JS hiện đại như React, hãy làm điều này bằng cách tạo một thành phần hiển thị liên kết của bạn và chuyển vào tên liên kết và căn chỉnh cho mỗi phần tử, có thể có hoặc có thể không giống nhau.

Sau đó, chỉ cần sử dụng:

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

Để cuộn mượt mà với độ lệch 100px.


12
Đây là cách tốt nhất để thực hiện điều này một cách suôn sẻ và đơn giản.
catch22

2
Phương pháp tốt nhất và bản địa nhất mà tôi nghĩ.
Даниил Пронин

1
Điều này rất đơn giản và trơn tru. Nhiều người khác chỉ thêm JS không cần thiết.
RedSands

2
Điều này đặc biệt hữu ích khi liên kết loại "đầu trang" nằm bên dưới thanh điều hướng và bạn muốn hiển thị điều hướng, nhưng không muốn bất kỳ liên kết nào khác bị lệch
Joe Moon

bạn có thể đạt được điều tương tự bằng cách sử dụng margin-top phủ định, bạn tránh bọc phần tử này cùng với phần tử khác có vị trí tương đối
Sergi

14

Tôi đã giải quyết vấn đề này bằng cách sử dụng,

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

Điều này làm cho phần tử xuất hiện trong centersau khi cuộn, vì vậy tôi không phải tính toán yOffset.

Hy vọng nó giúp...


1
Bạn không nên sử dụng scrollTotrên elementnhưng bậtwindow
Arseniy-II

@ Arseniy-II Cảm ơn bạn đã chỉ ra điều đó !!. Tôi đã bỏ lỡ phần đó!
Imtiaz Shakil Siddique

khối có giúp cho đầu trang không?
Ashok kumar Ganesan

11

Điều này phù hợp với tôi trong Chrome (Với khả năng cuộn mượt mà và không bị mất thời gian)

Nó chỉ di chuyển phần tử, bắt đầu cuộn, sau đó di chuyển nó trở lại.

Không có "popping" hiển thị nếu phần tử đã có trên màn hình.

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;

Đây là giải pháp tốt nhất ... Ước gì nó được đánh dấu là câu trả lời. Sẽ tiết kiệm được vài giờ.
Shivam

7

Dựa trên câu trả lời trước đó, tôi đang thực hiện điều này trong một dự án Angular5.

Bắt đầu với:

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

Nhưng điều này gây ra một số vấn đề và cần phải setTimeout cho scrollBy () như thế này:

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

Và nó hoạt động hoàn hảo trong MSIE11 và Chrome 68+. Tôi chưa thử nghiệm trong FF. 500ms là thời gian trễ ngắn nhất mà tôi muốn. Việc giảm tốc độ đôi khi không thành công vì cuộn trơn chưa hoàn thành. Điều chỉnh theo yêu cầu cho dự án của riêng bạn.

+1 cho Fred727 để có giải pháp đơn giản nhưng hiệu quả này.


6
Điều này cảm thấy hơi khó khăn khi cuộn mượt mà đến một phần tử và sau đó cuộn lên một chút sau đó.
bắt 22

6

Giả sử bạn muốn cuộn đến các div đều ở cùng cấp trong DOM và có tên lớp là "scroll-with-offset", thì CSS này sẽ giải quyết vấn đề:

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

Độ lệch từ đầu trang là 100px. Nó sẽ chỉ hoạt động như dự định với khối: 'start':

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

Điều đang xảy ra là điểm trên cùng của divs ở vị trí bình thường nhưng nội dung bên trong của chúng bắt đầu thấp hơn vị trí bình thường 100px. Đó là mục đích của padding-top: 100px. margin-bottom: -100px là để bù đắp phần dư thừa của div bên dưới. Để làm cho giải pháp hoàn chỉnh, hãy thêm CSS này để bù đắp lề / đệm cho div trên cùng và dưới cùng:

.top-div {
  padding-top: 0;
}
.bottom-div {
  margin-bottom: 0;
}

6

Giải pháp này thuộc về @ Arseniy-II , tôi vừa đơn giản hóa nó thành một hàm.

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

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

Cách sử dụng (bạn có thể mở bảng điều khiển ngay tại đây trong StackOverflow và kiểm tra nó):

_scrollTo('#question-header', 0);

4

Bạn cũng có thể sử dụng các element.scrollIntoView() tùy chọn

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

mà hầu hết các trình duyệt hỗ trợ


33
Tuy nhiên, điều này không hỗ trợ bất kỳ tùy chọn bù đắp nào.
adriendenat

3

Tôi đã có cái này và nó hoạt động tuyệt vời đối với tôi:

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

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

Hy vọng nó giúp.


2

Một giải pháp khác là sử dụng "offsetTop", như sau:

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

window.scrollTo({
  top: elementPosition - 10, //add your necessary value
  behavior: "smooth"  //Smooth transition to roll
});

2

Vì vậy, có lẽ điều này là một chút rắc rối nhưng cho đến nay rất tốt. Tôi đang làm việc trong góc 9.

tập tin .ts

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

tập tin .html

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

Tôi điều chỉnh độ lệch với lề và phần đệm trên cùng.

Saludo!


1

Đã tìm thấy một giải pháp thay thế. Giả sử bạn muốn cuộn đến một div, chẳng hạn như Element ở đây và bạn muốn có khoảng cách 20px phía trên nó. Đặt tham chiếu thành một div đã tạo ở trên nó:

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

Làm như vậy sẽ tạo ra khoảng cách mà bạn muốn.

Nếu bạn có tiêu đề, hãy tạo một div trống phía sau tiêu đề và gán cho nó một chiều cao bằng chiều cao của tiêu đề và tham chiếu đến nó.


0

Ý tưởng chính của tôi là tạo một tempDiv phía trên dạng xem mà chúng ta muốn cuộn đến. Nó hoạt động tốt mà không bị trễ trong dự án của tôi.

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' });
}

Ví dụ sử dụng

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

Hy vọng nó sẽ giúp


0

Dựa trên câu trả lời của Arseniy-II: Tôi đã có Use-Case trong đó thực thể cuộn không phải là cửa sổ mà là một Mẫu bên trong (trong trường hợp này là một div). Trong trường hợp này, chúng ta cần đặt một ID cho vùng chứa cuộn và lấy nó qua getElementByIdđể sử dụng chức năng cuộn của nó:

<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'
  });

Để nó ở đây phòng khi ai đó gặp phải trường hợp tương tự!


0

Đây là 2 xu của tôi.

Tôi cũng gặp phải vấn đề về việc cuộn scrollIntoView qua phần tử một chút, vì vậy tôi đã tạo một tập lệnh (javascript gốc) gắn trước một phần tử vào đích, định vị nó một chút lên đầu bằng css và cuộn đến phần tử đó. Sau khi cuộn, tôi lại xóa các phần tử đã tạo.

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>

Tệp 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;
}

Hy vọng điều này sẽ giúp bất cứ ai!


0

Tôi thêm mẹo css này cho những người không giải quyết được vấn đề này bằng các giải pháp ở trên:

#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: Tôi đã tạo một gói npm hoạt động tốt hơn giải pháp sau và dễ sử dụng hơn.

Chức năng SmoothScroll của tôi

Tôi đã sử dụng giải pháp tuyệt vời của Steve Banton và viết một hàm giúp sử dụng thuận tiện hơn. Nó sẽ dễ dàng hơn chỉ để sử dụng window.scroll()hoặc thậm chí window.scrollBy(), như tôi đã thử trước đây, nhưng hai cái này có một số vấn đề:

  • Mọi thứ trở nên lộn xộn sau khi sử dụng chúng với một hoạt động mượt mà trên.
  • Dù sao thì bạn cũng không thể ngăn cản chúng và phải đợi cho đến khi hết cuộn. Vì vậy, tôi hy vọng chức năng của tôi sẽ hữu ích cho bạn. Ngoài ra, có một polyfill nhẹ giúp nó hoạt động trong Safari và thậm chí cả IE.

Đây là mã

Chỉ cần sao chép nó và lộn xộn với nó bao giờ bạn muốn.

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));
};

Để tạo phần tử liên kết, chỉ cần thêm thuộc tính dữ liệu sau:

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

Ngoài ra, bạn có thể đặt một thuộc tính khác làm phụ đề

data-smooth-scroll-block="center"

Nó đại diện cho blocktùy chọn của scrollIntoView()chức năng. Theo mặc định, nó start. Đọc thêm trên MDN .

Cuối cùng

Điều chỉnh chức năng SmoothScroll theo nhu cầu của bạn.

Ví dụ: nếu bạn có một số tiêu đề cố định (hoặc tôi gọi nó bằng từ này masthead), bạn có thể làm như sau:

const mastheadEl = document.querySelector(someMastheadSelector);

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

const EXTRA_OFFSET = mastheadEl.offsetHeight - 3;

Nếu không gặp trường hợp như vậy thì bạn cứ xóa đi, tại sao không :-D.


0

Đối với một phần tử trong một hàng của bảng, hãy sử dụng JQuery để lấy hàng phía trên hàng bạn muốn và thay vào đó, chỉ cần cuộn đến hàng đó.

Giả sử tôi có nhiều hàng trong một bảng, một số hàng sẽ được quản trị viên xem xét. Mỗi hàng yêu cầu đánh giá có cả mũi tên lên và xuống để đưa bạn đến mục trước đó hoặc mục tiếp theo để xem xét.

Đây là một ví dụ hoàn chỉnh sẽ chỉ chạy nếu bạn tạo một tài liệu HTML mới trong notepad và lưu nó. Có thêm mã để phát hiện phần đầu và phần cuối của các mục của chúng tôi để xem xét, vì vậy chúng tôi không tạo ra bất kỳ lỗi nào.

<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>

-1

Giải pháp đơn giản để cuộn phần tử cụ thể xuống

const element = document.getElementById("element-with-scroll");
element.scrollTop = element.scrollHeight - 10;
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.