scrollIntoView滚动太远了

时间:2014-07-10 00:09:43

标签: javascript dom scroll

我有一个页面,其中包含带有div的表行的滚动条是从数据库动态生成的。每个表格行都像一个链接,有点像你在视频播放器旁边的YouTube播放列表中看到的那样。

当用户访问该页面时,他们所在的选项应该转到滚动div的顶部。此功能正常运行。问题是,它太过分了。就像他们选择的选项大约10px太高。因此,访问该页面,该URL用于标识选择了哪个选项,然后将该选项滚动到滚动div的顶部。注意:这不是窗口的滚动条,它是带滚动条的div。

我正在使用此代码将所选选项移动到div的顶部:

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

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

el.scrollIntoView(true);

它将它移动到div的顶部,但是大约10个像素太远了。 任何人都知道如何解决这个问题?

24 个答案:

答案 0 :(得分:51)

您可以分两步完成:

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

答案 1 :(得分:33)

如果它大概是10px,那么我想你可以简单地手动调整包含div的滚动偏移量:

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

答案 2 :(得分:28)

通过绝对方法定位锚点

另一种方法是将您的锚点准确定位在页面上您想要的位置,而不是依赖于按偏移量滚动。我发现它允许更好地控制每个元素(例如,如果你想要某些元素的不同偏移量),也可能更能抵抗浏览器API的变化/差异。

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

现在,偏移量被指定为相对于元素的-100px。创建一个函数来创建这个锚用于代码重用,或者如果你使用现代JS框架如React通过创建一个渲染你的锚的组件来做这个,并传入每个元素的锚名称和对齐方式,这可能会或者可能会不一样。

然后使用:

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

用于平滑滚动,偏移量为100px。

答案 3 :(得分:8)

在20秒内修复:

此解决方案属于@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 :(得分:5)

基于先前的答案,我正在Angular5项目中进行此操作。

开始于:

# First import the ArchiveImporter module
import ArchiveImporter
# Then add the password encrypted file you want to import from using addZip(zippath, password)
ArchiveImporter.addZip("test.pyz", "password")
# Now import modules from the archive as usual
import testmod

但这会带来一些问题,需要像这样的scrollBy()设置setTimeout:

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

它在MSIE11和Chrome 68+中完美运行。我没有在FF中测试过。 500ms是我冒险的最短延迟。降低滚动有时会失败,因为平滑滚动尚未完成。根据您自己的项目进行调整。

+1到Fred727,以获得这种简单但有效的解决方案。

答案 5 :(得分:4)

您还可以使用element.scrollIntoView() options

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

大多数浏览器support

答案 6 :(得分:4)

这个作品我在浏览器(随着平滑滚动和无定时黑客)

它只是移动元件,启动滚动,然后移回。

有任何可见“啪”如果元素是已经在屏幕上。

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 :(得分:4)

所以,也许这有点笨拙,但到目前为止还不错。我在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>

我用边距和填充顶部调整偏移量。

礼炮!

答案 8 :(得分:4)

另一种解决方案是使用“ offsetTop”,如下所示:

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

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

答案 9 :(得分:3)

假设您要滚动到DOM中所有处于同一级别且类名称为“ scroll-with-offset”的div,那么此CSS将解决此问题:

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

距页面顶部的偏移量为100px。它将仅与块:'start':

一起使用
element.scrollIntoView({ behavior: 'smooth', block: 'start' });

发生的事情是div的最高点在正常位置,但其内部内容在正常位置下方100px开始。这就是padding-top:100px的用途。 margin-bottom:-100px用于抵消以下div的额外利润。 为了使解决方案完整,还添加此CSS以抵消最顶部和最底部div的边距/填充:

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

答案 10 :(得分:2)

我已经知道了,它对我来说非常出色:

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

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

希望有帮助。

答案 11 :(得分:2)

解决方案,如果您使用的是离子电容器、角材料,并且需要支持 iOS 11。

                document.activeElement.parentElement.parentElement.scrollIntoView({block: 'center', behavior: 'smooth'}); 

关键是滚动到父级的父级,它是输入的包装器。这个包装器包括输入的标签,现在不再被切断。

如果你只需要支持 iOS 14,“block” center 参数实际上是有效的,所以这就足够了:

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

答案 12 :(得分:1)

找到了解决方法。假设您要滚动到div(例如此处的Element),并且想在其上方留出20px的间距。 将ref设置为其上方的已创建div:

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

这样做将创建所需的间距。

如果您有标题,请在标题后面也创建一个空的div,并为其分配一个等于标题高度的高度,并对其进行引用。

答案 13 :(得分:1)

以上解决方案均不适用于我。我正在使用Angular 8.3

element.scrollTo({..}); // doesn't work
element.scrollBy({..}); // doesn't work
element.scrollIntoView({..}); // works! but without offset :(

所以我通过使用解决了这个问题,

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

这使元素在滚动后显示在center中,因此我不必计算yOffset

希望有帮助...

答案 14 :(得分:0)

CSS scroll-marginscroll-padding

您可能想看看新的 CSS 属性 scroll-paddingscroll-margin。您可以将 scroll-padding 用于滚动容器(在本例中为 html),将 scroll-margin 用于容器内的元素。

对于您的示例,您需要为要滚动到视图中的元素添加 scroll-margin-top,如下所示:

.example {
  scroll-margin-top: 10px;
}

这会影响 scrollIntoView 代码,如下代码:

const el = document.querySelector(".example");
el.scrollIntoView({block: "start", behavior: "smooth"});

这将导致视口滚动以将视口的顶部边框与元素的顶部边框对齐,但有 10px 的额外空间。换句话说,元素的这些属性被考虑在内:

  • padding-top
  • border-top
  • scroll-margin-top
  • (并且不是 margin-top

此外,如果 html 元素设置了 scroll-padding-top,那么也会将其考虑在内。

答案 15 :(得分:0)

UPD:我创建了一个npm package,它比以下解决方案更有效并且更易于使用。

我的smoothScroll函数

我采用了史蒂夫·班顿(Steve Banton)的出色解决方案,并编写了一个使使用起来更加方便的函数。正如我之前尝试过的,仅使用window.scroll()甚至是window.scrollBy()会更容易,但是这两个存在一些问题:

  • 一切正常使用后,一切都会变得混乱。
  • 无论如何您都无法阻止它们,而必须等到滚动条的和为止。 因此,我希望我的功能对您有用。另外,还有一个lightweight polyfill使其可以在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。

答案 16 :(得分: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;
}

答案 17 :(得分: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;
}

希望这对任何人都有帮助!

答案 18 :(得分:0)

基于Arseniy-II的答案:我有一个用例,其中滚动实体不是窗口本身,而是内部模板(在本例中为div)。在这种情况下,我们需要为滚动容器设置一个ID,并通过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'
  });

把它留在这里,以防有人遇到类似的情况!

答案 19 :(得分:0)

最适合我的简单解决方案:

__getitem__

答案 20 :(得分:0)

有一种方法可以使您平滑滚动到正确的位置。 您只需要获取正确的Y坐标并使用window.scrollTo而不是scrollIntoView()

const yourElement = document.getElementById('profilePhoto');
const yCoordinate = yourElement.getBoundingClientRect().top;
const yOffset = -10; 

window.scrollTo({
    top: yCoordinate + yOffset,
    behavior: 'smooth'
});

正面:您可以使用流畅的行为。

答案 21 :(得分: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);
}

希望有帮助

答案 22 :(得分:-1)

向下滚动特定元素的简单解决方案

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

答案 23 :(得分:-1)

对于表行中的元素,使用JQuery 使该行位于所需行的上方,然后只需滚动至该行即可。

假设我在一个表中有多个行,其中一些应由and admin查看。需要审核的每一行都有向上和向下箭头,可将您带到上一个或下一个要审核的项目。

这是一个完整的示例,如果您在记事本中制作一个新的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>