scrollIntoView Scrolls terlalu jauh

113

Saya memiliki halaman di mana bilah gulir yang berisi baris tabel dengan div di dalamnya dihasilkan secara dinamis dari database. Setiap baris tabel berfungsi seperti link, seperti yang Anda lihat di playlist YouTube di samping pemutar video.

Saat pengguna mengunjungi halaman, opsi tempat mereka berada seharusnya berada di bagian atas div gulir. Fungsionalitas ini berfungsi. Masalahnya adalah itu berjalan terlalu jauh. Seperti opsi yang mereka gunakan sekitar 10px terlalu tinggi. Jadi, halaman tersebut dikunjungi, url digunakan untuk mengidentifikasi opsi mana yang dipilih dan kemudian menggulir opsi itu ke bagian atas div gulir. Catatan: Ini bukan bilah gulir untuk jendela, ini adalah div dengan bilah gulir.

Saya menggunakan kode ini untuk membuatnya memindahkan opsi yang dipilih ke bagian atas div:

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

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

el.scrollIntoView(true);

Ini memindahkannya ke bagian atas div tetapi sekitar 10 piksel terlalu jauh. Ada yang tahu cara memperbaikinya?

Matthew Wilson
sumber
47
Kurangnya konfigurasi offset untuk scrollIntoViewmengganggu.
mystrdat

Jawaban:

59

Jika itu sekitar 10px, maka saya kira Anda bisa secara manual menyesuaikan divscroll offset yang mengandung seperti itu:

el.scrollIntoView(true);
document.getElementById("containingDiv").scrollTop -= 10;
Lucas Trzesniewski
sumber
2
Apakah ini akan memiliki efek animasi yang sama seperti scrollIntoView?
1252748
1
@ Thomas AFAIK, scrollIntoViewfungsi DOM biasa tidak menyebabkan animasi. Apakah Anda berbicara tentang plugin jQuery scrollIntoView?
Lucas Trzesniewski
1
Itu berhasil, kecuali itu adalah arah yang salah jadi yang harus saya lakukan hanyalah mengubahnya dari + = 10 menjadi - = 10 dan sekarang dimuat dengan benar, terima kasih banyak atas bantuannya !!!!
Matthew Wilson
Ya. Saya bingung. Saya pikir itu animasi. Maaf!
1252748
17
Itu bisa beranimasi, tambahkan saja el.scrollIntoView({ behavior: 'smooth' });. Dukungan tidak bagus!
daveaspinall
115

Gulir dengan mulus ke posisi yang benar

Dapatkan benar y mengkoordinasikan dan penggunaanwindow.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'});
Arseniy-II
sumber
2
Ini adalah jawaban yang paling benar - kita dapat dengan mudah menemukan posisi scroll dengan jQuery('#el').offset().topdan kemudian menggunakanwindow.scrollTo
Alexander Goncharov
1
1 untuk solusi ini. Ini membantu saya untuk menggulir ke elemen dengan benar ketika saya memiliki wadah yang diposisikan relatif dengan offset atas.
Liga
Siapa pun yang datang ke sini menggunakan HashLink dengan React Router, inilah yang berhasil untuk saya. Di prop gulir di HashLink Anda, scroll={el => { const yCoordinate = el.getBoundingClientRect().top + window.pageYOffset; const yOffset = -80; window.scrollTo({ top: yCoordinate + yOffset, behavior: 'smooth' }); }}- di mana offsetnya 10-15 piksel lebih besar dari ukuran header Anda hanya untuk jarak yang lebih baik
Rob B
86

Anda dapat melakukannya dalam dua langkah:

el.scrollIntoView(true);
window.scrollBy(0, -10); // Adjust scrolling with a negative value here
fred727
sumber
23
Jika scrollIntoView()pengguliran belum selesai sebelum scrollBy()dijalankan, pengguliran terakhir akan membatalkan pengguliran sebelumnya . Setidaknya di Chrome 71.
Dave Hughes
1
Dalam hal ini gunakan setTimeout seperti yang terlihat pada jawaban di bawah ini: stackoverflow.com/a/51935129/1856258 .. Bagi saya ini berfungsi tanpa Timeout. Terima kasih!
leole
apakah ada cara untuk langsung memasukkan korektor piksel ke dalam scrollIntoView agar aplikasi langsung pindah ke tempat yang relevan? Seperti scrollIntoView({adjustment:y}), saya pikir itu harus mungkin dengan kode khusus di akhir
Webwoman
80

Posisi Jangkar Dengan Metode Mutlak

Cara lain untuk melakukan ini adalah dengan memposisikan jangkar tepat di tempat yang Anda inginkan pada halaman daripada mengandalkan pengguliran dengan offset. Saya merasa ini memungkinkan kontrol yang lebih baik untuk setiap elemen (misalnya jika Anda menginginkan offset yang berbeda untuk elemen tertentu), dan mungkin juga lebih tahan terhadap perubahan / perbedaan API browser.

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

Sekarang offset ditentukan sebagai -100px relatif terhadap elemen. Buat fungsi untuk membuat jangkar ini untuk digunakan kembali kode, atau jika Anda menggunakan kerangka kerja JS modern seperti React lakukan ini dengan membuat komponen yang membuat jangkar Anda, dan berikan nama jangkar dan penyelarasan untuk setiap elemen, yang mungkin atau mungkin tidak sama.

Kemudian gunakan saja:

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

Untuk pengguliran mulus dengan offset 100px.

Steve Banton
sumber
12
Sejauh ini, ini adalah cara terbaik untuk menerapkan ini dengan lancar dan sederhana.
Tangkapan22
2
Metode terbaik dan paling asli menurut saya.
Даниил Пронин
1
Ini sangat sederhana dan apik. Banyak dari yang lain hanya menambahkan JS yang tidak dibutuhkan.
RedSands
2
Ini sangat berguna ketika tautan jenis "halaman atas" berada di bawah bilah navigasi, dan Anda ingin menampilkan navigasi, tetapi tidak ingin tautan lain diimbangi
Joe Moon
Anda dapat mencapai hal yang sama menggunakan margin-top negatif, Anda menghindari membungkus elemen bersama dengan yang lain dengan relatif posisi
Sergi
18

Saya memecahkan masalah ini dengan menggunakan,

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

Ini membuat elemen muncul centersetelah scrolling, jadi saya tidak perlu menghitung yOffset.

Semoga membantu ...

Imtiaz Shakil Siddique
sumber
1
Anda harus menggunakan scrollTobukan pada elementtapi padawindow
Arseniy-II
@ Arseniy-II Terima kasih telah menunjukkan hal itu !!. Saya melewatkan bagian itu!
Imtiaz Shakil Siddique
apakah blok tersebut membantu untuk atas?
Ashok kumar Ganesan
13

Ini berfungsi untuk saya di Chrome (Dengan pengguliran halus dan tidak ada peretasan waktu)

Itu hanya memindahkan elemen, memulai gulungan, lalu memindahkannya kembali.

Tidak ada "popping" yang terlihat jika elemen sudah ada di layar.

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;
Ryan Bagaimana
sumber
Ini adalah solusi terbaik ... Berharap itu ditandai sebagai jawaban. Akan menghemat beberapa jam.
Shivam
Mengujinya dan berhasil di luar kotak. Bukan peretasan dan berfungsi dengan baik! Dukung yang ini!
Jens Törnell
7

Membangun jawaban sebelumnya, saya melakukan ini dalam proyek Angular5.

Dimulai dengan:

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

Tapi ini memberi beberapa masalah dan perlu setTimeout untuk scrollBy () seperti ini:

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

Dan bekerja dengan sempurna di MSIE11 dan Chrome 68+. Saya belum menguji di FF. 500ms adalah penundaan terpendek yang saya berani. Turunkan terkadang gagal karena gulungan halus belum selesai. Sesuaikan sesuai kebutuhan untuk proyek Anda sendiri.

1 untuk Fred727 untuk solusi sederhana namun efektif ini.

PrimaryW
sumber
6
Ini terasa agak tersendat untuk menggulir dengan mulus ke suatu elemen dan kemudian menggulir ke atas sedikit setelahnya.
Tangkapan22
6

Dengan asumsi Anda ingin menggulir ke div yang semuanya berada pada level yang sama di DOM dan memiliki nama kelas "scroll-with-offset", maka CSS ini akan menyelesaikan masalah:

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

Offset dari bagian atas halaman adalah 100px. Ini hanya akan bekerja sebagaimana dimaksud dengan blok: 'mulai':

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

Apa yang terjadi adalah bahwa titik teratas div berada di lokasi normal tetapi konten dalamnya mulai 100px di bawah lokasi normal. Itulah gunanya padding-top: 100px. margin-bottom: -100px adalah untuk mengimbangi margin ekstra div di bawah ini. Untuk membuat solusi lengkap juga tambahkan CSS ini untuk mengimbangi margin / paddings untuk div paling atas dan paling bawah:

.top-div {
  padding-top: 0;
}
.bottom-div {
  margin-bottom: 0;
}
Georgy Petukhov
sumber
6

Solusi ini milik @ Arseniy-II , saya baru saja menyederhanakannya menjadi sebuah fungsi.

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

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

Penggunaan (Anda dapat membuka konsol di sini di StackOverflow dan mengujinya):

_scrollTo('#question-header', 0);
Diego Fortes
sumber
4

Anda juga dapat menggunakan element.scrollIntoView() opsi

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

yang kebanyakan browser dukungan

Nicholas Murray
sumber
34
Namun ini tidak mendukung opsi offset apa pun.
adriendenat
3

Saya mendapatkan ini dan ini bekerja dengan sangat baik untuk saya:

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

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

Semoga membantu.

Fernando Brandao
sumber
2

Solusi lain adalah dengan menggunakan "offsetTop", seperti ini:

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

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

Jadi, mungkin ini agak kikuk tapi sejauh ini bagus. Saya bekerja di sudut 9.

mengajukan .ts

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

mengajukan .html

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

Saya menyesuaikan offset dengan margin dan padding top.

Saludos!

Ignacio Basti
sumber
1

Menemukan solusi solusi. Katakanlah Anda ingin menggulir ke div, Elemen di sini misalnya, dan Anda ingin memiliki jarak 20px di atasnya. Setel ref ke div yang dibuat di atasnya:

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

Melakukannya akan membuat jarak yang Anda inginkan.

Jika Anda memiliki header, buat div kosong juga di belakang header dan tetapkan tinggi yang sama dengan tinggi header dan referensikan.

Ibrahim Al Masri
sumber
0

Ide utama saya adalah membuat tempDiv di atas tampilan yang ingin kita gulir. Ini bekerja dengan baik tanpa ketinggalan dalam proyek saya.

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

Contoh penggunaan

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

Semoga membantu

Phan Van Linh
sumber
0

Berdasarkan jawaban Arseniy-II: Saya memiliki Use-Case di mana entitas scrolling bukan jendela itu sendiri tetapi Template dalam (dalam hal ini div). Dalam skenario ini kita perlu menetapkan ID untuk wadah pengguliran dan mendapatkannya melalui getElementByIduntuk menggunakan fungsi penggulirannya:

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

Meninggalkannya di sini seandainya seseorang menghadapi situasi serupa!

kounex
sumber
0

Ini 2 sen saya.

Saya juga memiliki masalah scrollIntoView yang menggulir sedikit melewati elemen, jadi saya membuat skrip (javascript asli) yang menambahkan elemen ke tujuan, memposisikannya sedikit ke atas dengan css dan menggulir ke yang itu. Setelah menggulir, saya menghapus elemen yang dibuat lagi.

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>

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

Semoga ini bisa membantu siapa pun!

rubeus
sumber
0

Saya menambahkan tip css ini untuk mereka yang tidak menyelesaikan masalah ini dengan solusi di atas:

#myDiv::before {
  display: block;
  content: " ";
  margin-top: -90px; // adjust this with your header height
  height: 90px; // adjust this with your header height
  visibility: hidden;
}
Jérémy MARQUER
sumber
0

UPD: Saya telah membuat paket npm yang bekerja lebih baik daripada solusi berikut dan lebih mudah digunakan.

Fungsi smoothScroll saya

Saya telah mengambil solusi luar biasa dari Steve Banton dan menulis fungsi yang membuatnya lebih nyaman digunakan. Akan lebih mudah untuk digunakan window.scroll()atau bahkan window.scrollBy(), seperti yang pernah saya coba sebelumnya, tetapi keduanya memiliki beberapa masalah:

  • Semuanya menjadi bermutu rendah setelah menggunakannya dengan perilaku halus.
  • Anda tidak dapat mencegahnya dan harus menunggu hingga gulungan dan. Jadi saya berharap fungsi saya bermanfaat untuk Anda. Juga, ada polyfill ringan yang membuatnya berfungsi di Safari dan bahkan IE.

Ini kodenya

Cukup salin dan ubahlah sesuka Anda.

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

Untuk membuat elemen link cukup tambahkan atribut data berikut:

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

Anda juga dapat mengatur atribut lain sebagai tambahan

data-smooth-scroll-block="center"

Ini mewakili blockopsi scrollIntoView()fungsi. Secara default, ini start. Baca lebih lanjut tentang MDN .

Akhirnya

Sesuaikan fungsi smoothScroll dengan kebutuhan Anda.

Misalnya, jika Anda memiliki beberapa tajuk tetap (atau saya menyebutnya dengan kata masthead), Anda dapat melakukan sesuatu seperti ini:

const mastheadEl = document.querySelector(someMastheadSelector);

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

const EXTRA_OFFSET = mastheadEl.offsetHeight - 3;

Jika Anda tidak memiliki kasus seperti itu, maka hapus saja, mengapa tidak :-D.

Dmitry Zakharov
sumber
-1

Solusi sederhana untuk menggulir elemen tertentu ke bawah

const element = document.getElementById("element-with-scroll");
element.scrollTop = element.scrollHeight - 10;
Huri
sumber
-1

Untuk elemen dalam baris tabel, gunakan JQuery untuk mendapatkan baris di atas yang Anda inginkan , dan cukup gulir ke baris itu sebagai gantinya.

Misalkan saya memiliki beberapa baris dalam sebuah tabel, beberapa di antaranya harus ditinjau oleh dan admin. Setiap baris yang perlu ditinjau memiliki panah atas dan bawah untuk membawa Anda ke item sebelumnya atau berikutnya untuk ditinjau.

Berikut adalah contoh lengkap yang seharusnya dijalankan jika Anda membuat dokumen HTML baru di notepad dan menyimpannya. Ada kode tambahan untuk mendeteksi bagian atas dan bawah item kami untuk ditinjau sehingga kami tidak membuat kesalahan apa pun.

<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>
Martin Francis
sumber