ChatGPT解决这个技术问题 Extra ChatGPT

scrollIntoView 滚动得太远

我有一个页面,其中包含从数据库动态生成的带有 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 个像素。有谁知道如何解决这个问题?

scrollIntoView 缺少偏移配置令人不安。
@mystrdat 有新的 CSS 属性可以处理这个问题,scroll-marginscroll-padding

A
Arseniy-II

平滑滚动到合适的位置

获取正确 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'});

这是最正确的答案 - 我们可以使用 jQuery('#el').offset().top 轻松找到滚动位置,然后使用 window.scrollTo
对此解决方案 +1。当我有一个相对于顶部偏移定位的容器时,它帮助我正确滚动到元素。
任何来这里使用 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 像素,只是为了更好的间距
这是第一个真正帮助我的答案
完美的。非常感谢!
R
Rohit Sharma

绝对法定位锚

另一种方法是将锚点准确定位在页面上您想要的位置,而不是依靠偏移量滚动。我发现它可以更好地控制每个元素(例如,如果您希望某些元素具有不同的偏移量),并且还可能更能抵抗浏览器 API 更改/差异。

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

现在偏移量指定为相对于元素的 -100px。创建一个函数来创建此锚点以进行代码重用,或者如果您正在使用诸如 React 之类的现代 JS 框架,请通过创建一个呈现锚点的组件来执行此操作,并为每个元素传递锚点名称和对齐方式,这可能是也可能是不一样。

然后只需使用:

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

用于偏移 100px 的平滑滚动。


这是迄今为止顺利和简单地实现这一点的最佳方式。
我认为最好和最原生的方法。
这是如此简单和流畅。许多其他人只是添加了不需要的 JS。
当“页面顶部”类型的链接位于导航栏下方并且您想要显示导航但不希望任何其他链接偏移时,这尤其有用
您可以使用负边距顶部来实现相同的效果,避免将元素与另一个元素与位置相对
f
fred727

您可以分两步完成:

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

如果在 scrollBy() 运行之前 scrollIntoView() 尚未完成滚动,则后者的滚动将取消前者。至少在 Chrome 71 上。
在这种情况下,请使用 setTimeout,如下面的答案所示:stackoverflow.com/a/51935129/1856258 .. 对我来说,它可以在没有超时的情况下工作。谢谢!
有没有办法将像素的校正器直接注入 scrollIntoView 以使应用程序直接移动到相关位置?像scrollIntoView({adjustment:y})一样,我认为应该可以在最后使用自定义代码
M
Meta-Knight

如果它大约是 10 像素,那么我想您可以像这样简单地手动调整包含 div 的滚动偏移量:

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

这会产生与 scrollIntoView 相同的动画效果吗?
@thomas AFAIK,普通的 DOM scrollIntoView 函数不会产生动画。你在谈论 scrollIntoView jQuery 插件吗?
那个工作的人,除了它是错误的方向,所以我所要做的就是将它从 += 10 更改为 -= 10 现在它加载得恰到好处,非常感谢您的帮助!!!!
是的。我很困惑。我以为是动画的。对不起!
它可以动画,只需添加 el.scrollIntoView({ behavior: 'smooth' });。虽然支持不是很好!
I
Imtiaz Shakil Siddique

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

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

这使得滚动后元素出现在center中,因此我不必计算yOffset

希望能帮助到你...


您不应该在 element 上而是在 window 上使用 scrollTo
@Arseniy-II 感谢您指出这一点!!。我错过了那部分!
积木对顶部有帮助吗?
我的卷轴只是稍微偏离了一点!它适用于此!谢谢!
很好,这应该是顶级评论!
s
schettino72

这在 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;

这是最好的解决方案......希望它被标记为答案。本来可以节省几个小时的。
对其进行了测试,它开箱即用。不是黑客,效果很好!投票给这个!
惊人。这种方式(存储 style.top,设置为 -x 并稍后在 scrollIntoView 之后重新应用它是唯一在 Blazor 中有效且实际有效的方法。将它与 Virtualize 组件一起使用以显示大量数据。得知没有滚动时感到震惊以任何方式支持!!谢谢
完美的!!我必须登录才能喜欢你:)))
F
Flimm

CSS 滚动边距和滚动填充

您可能想看看新的 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 的额外空间。换句话说,元素的这些属性被考虑在内:

填充顶部

边框顶部

滚动边距顶部

(而不是边缘顶部)

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


如果您使用的是 Vue 之类的框架,您可能需要确保在运行 scrollIntoView 之前完成对 DOM 的所有更改,方法是使用类似 Vue.nextTick(() => el.scrollIntoView())
这是一个很棒的解决方案..这完全有道理:) 谢谢@Flimm
完美的谢谢队友!选定的答案对我来说不太适用。
2021年最佳答案!
另一个疑难解答提示:如果您正在加载带有片段的 URL(例如 example.com/#test),那么当页面加载时,浏览器将滚动到带有该 idname 的元素,但不是在所有 AJAX 之前已完成加载。如果您在页面加载后导致布局发生变化,则导航到带有片段的 URL 可能不会滚动到预期位置。
D
Diego Fortes

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

我目前正在生产中使用它,它工作得很好。


这对我来说是一种享受,我还添加了一条线来动态确定所需的偏移量。我的内容位于导航下方,因此我获取了导航高度以作为 yOffset const yOffset = document.getElementById('nav_id').getBoundingClientRect().height 传入我还必须稍微修改为 - yOffset 而不是 +,因为我的内容滚动得太远向上页面(在导航下)
A
AmerllicA

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

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

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

P
PrimaryW

基于较早的答案,我在 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


平滑滚动到一个元素然后向上滚动一点感觉有点笨拙。
G
Georgy Petukhov

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

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

与页面顶部的偏移量为 100px。它只会按预期使用 block: '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;
}

F
Fernando Brandao

我有这个,它对我来说非常有用:

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

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

希望能帮助到你。


在 vue 中不起作用,有时会错过滚动位置
x
xiawi

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

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

萨鲁多斯!


N
Nicholas Murray

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

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

大多数浏览器support


但是,这不支持任何偏移选项。
j
jfbloom22

如果您使用的是 Ionic Capacitor、Angular Material 并且需要支持 iOS 11 的解决方案。

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

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

如果您只需要支持 iOS 14,“块”中心参数实际上可以工作,所以这就足够了:

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

M
Malik Zahid

最简单的方法是,

html, body {
    scroll-behavior: smooth;
}

    html [id], body [id] {
        scroll-margin: 50px !important;
    }

使用您提供的代码

var pathArray = window.location.pathname.split( '/' );
var el = document.getElementById(pathArray[5]);
el.style.scrollMargin = 50px !important;
el.scrollIntoView(true);

I
Ibrahim Al Masri

找到了解决方法。假设您要滚动到一个 div,例如此处的 Element,并且您希望在其上方有 20px 的间距。将 ref 设置为在其上方创建的 div:

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

这样做将创建您想要的间距。

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


L
Linh

我的主要想法是在我们想要滚动到的视图上方创建一个 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);
}

希望有帮助


k
kounex

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

把它留在这里以防有人面临类似的情况!


r
rubeus

这是我的 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;
}

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


J
Jérémy MARQUER

我为那些没有通过上述解决方案解决此问题的人添加此 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;
}

D
Dmitry Zakharov

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

我的 smoothScroll 功能

我采用了 Steve Banton 的绝妙解决方案,并编写了一个函数,使其使用起来更方便。正如我之前尝试过的那样,仅使用 window.scroll() 甚至 window.scrollBy() 会更容易,但这两个存在一些问题:

使用它们并具有流畅的行为后,一切都变得垃圾。

无论如何您都无法阻止它们,而必须等到卷轴的和。所以我希望我的功能对你有用。此外,还有一个轻量级的 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"

它表示 scrollIntoView() 函数的 block 选项。默认情况下,它是 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。


R
Radu Ionescu

我尝试使用上面的一些答案,但滚动不起作用。经过一番挖掘,我注意到滚动位置实际上非常奇怪(甚至是负值),这是由 Angular 如何渲染 router-outlet 中的页面造成的。

我的解决方案是从页面顶部开始计算元素的滚动位置。我在模板的开头放置了一个 id 为 start-of-page 的元素,并通过减去目标片段位置获得滚动量。

 ngAfterViewInit(): void {
    this.route.fragment.subscribe(fragment => {
      try {
        // @ts-ignore
        // document.querySelector('#hero').scrollIntoView({behavior:smooth, block: "start", inline: "start"});
        // @ts-ignore
        // document.querySelector('#' + fragment).scrollIntoView({behavior:smooth, block: "start", inline: "start"});
        this._scrollTo('#' + fragment,0);
      } catch (e) { };
    });
  }

private _scrollTo(selector: any, yOffset = 0){
    const base = document.querySelector('#start-of-page');
    const el = document.querySelector(selector);
    // @ts-ignore
    const y = el.getBoundingClientRect().top - base.getBoundingClientRect().top; 
    window.scrollTo({top: y, behavior: 'smooth'});
  }

A
Anomaly

一种解决方案是创建一个相对于目标偏移的不可见临时元素,滚动到它,然后将其删除。这可能并不理想,但在我工作的环境中,其他解决方案并不适合我。例如:

const createOffsetElement = (element) => {
    const offsetElement = document.createElement('div');
    offsetElement.style = `
        position: relative;
        top: -10em;
        height: 1px;
        width: 1px;
        visibility: hidden;
    `;
    const inputWrapper = element.closest('.ancestor-element-class');
    inputWrapper.appendChild(offsetElement);
    return offsetElement;
};

const scrollTo = (element) => {
    const offsetElement = createOffsetElement(element);
    offsetElement.scrollIntoView({
        behavior: 'smooth',
    });
    offsetElement.remove();
};

M
Martin Francis

对于表格行中的元素,使用 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>