Asked  4 Months ago    Answers:  5   Viewed   529 times

I have this code for my website:

function clickMe() {
  var element = document.getElementById('about');
  element.scrollIntoView({
    block: 'start',
    behavior: 'smooth',
  });
}

This works pretty nice but I have a fixed header so when the code scrolls to the element the header is in the way.

Is there a way to have an offset and make it scroll smoothly?

 Answers

52

Is there a way to have an offset and make it scroll smoothly?

Yes, but not with scrollIntoView()

The scrollIntoViewOptions of Element.scrollIntoView() do not allow you to use an offset. It is solely useful when you want to scroll to the exact position of the element.

You can however use Window.scrollTo() with options to both scroll to an offset position and to do so smoothly.

If you have a header with a height of 30px for example you might do the following:

function scrollToTargetAdjusted(){
    var element = document.getElementById('targetElement');
    var headerOffset = 45;
    var elementPosition = element.getBoundingClientRect().top;
    var offsetPosition = elementPosition - headerOffset;

    window.scrollTo({
         top: offsetPosition,
         behavior: "smooth"
    });
}

This will smoothly scroll to your element just so that it is not blocked from view by your header.

Note: You substract the offset because you want to stop before you scroll your header over your element.

See it in action

You can compare both options in the snippet below.

<script type="text/javascript">
  function scrollToTarget() {

    var element = document.getElementById('targetElement');
    element.scrollIntoView({
      block: "start",
      behavior: "smooth",
    });
  }

  function scrollToTargetAdjusted() {
    	var element = document.getElementById('targetElement');
      var headerOffset = 45;
    	var elementPosition = element.getBoundingClientRect().top;
      var offsetPosition = elementPosition - headerOffset;
      
      window.scrollTo({
          top: offsetPosition,
          behavior: "smooth"
      });   
  }

  function backToTop() {
    window.scrollTo(0, 0);
  }
</script>

<div id="header" style="height:30px; width:100%; position:fixed; background-color:lightblue; text-align:center;"> <b>Fixed Header</b></div>

<div id="mainContent" style="padding:30px 0px;">

  <button type="button" onclick="scrollToTarget();">element.scrollIntoView() smooth, header blocks view</button>
  <button type="button" onclick="scrollToTargetAdjusted();">window.scrollTo() smooth, with offset</button>

  <div style="height:1000px;"></div>
  <div id="targetElement" style="background-color:red;">Target</div>
  <br/>
  <button type="button" onclick="backToTop();">Back to top</button>
  <div style="height:1000px;"></div>
</div>
Tuesday, June 29, 2021
 
Sabya
answered 4 Months ago
81

Behavior options aren't fully supported in IE/Edge/Safari, so you'd have to implement something on your own. I believe jQuery has something already, but if you're not using jQuery, here's a pure JavaScript implementation:

function SmoothVerticalScrolling(e, time, where) {
    var eTop = e.getBoundingClientRect().top;
    var eAmt = eTop / 100;
    var curTime = 0;
    while (curTime <= time) {
        window.setTimeout(SVS_B, curTime, eAmt, where);
        curTime += time / 100;
    }
}

function SVS_B(eAmt, where) {
    if(where == "center" || where == "")
        window.scrollBy(0, eAmt / 2);
    if (where == "top")
        window.scrollBy(0, eAmt);
}

And if you need horizontal scrolling:

function SmoothHorizontalScrolling(e, time, amount, start) {
    var eAmt = amount / 100;
    var curTime = 0;
    var scrollCounter = 0;
    while (curTime <= time) {
        window.setTimeout(SHS_B, curTime, e, scrollCounter, eAmt, start);
        curTime += time / 100;
        scrollCounter++;
    }
}

function SHS_B(e, sc, eAmt, start) {
    e.scrollLeft = (eAmt * sc) + start;
}

And an example call is:

SmoothVerticalScrolling(myelement, 275, "center");
Thursday, June 3, 2021
 
Jauco
answered 5 Months ago
95

It is supported yes, but user experience is... bad.

As @9bits pointed out, this has long been supported by all major browsers. Not to worry about that. The main problem is the way that it works. It simply jumps to a particular element that may as well be at the end of the page. By jumping to it, users have no idea whether:

  • page has been scrolled up
  • page has been scrolled down
  • they've been redirected elsewhere

The first two can be determined by scroll position, but who says users kept track of scroll position before jump was done? So it's an nondeterministic action.

The last one may be true especially if the page has moving header that gets scrolled out of view and remaining page design doesn't imply anything on being on the same page (if it also doesn't have any total height vertical element like left menu bar). You'd be surprised how many pages have this problem. just check them out yourself. Go to some page, look at it at top, then press End key and look at it again. It is likely that you'll think it's a different page.

Animated scrollintoview jQuery plugin to the rescue

That's why there still are plugins that perform scroll into view instead of using native DOM function. They usually animate scrolling which eliminates all 3 issues outlined above. Users can easily keep track of the movement.

Saturday, June 12, 2021
 
zIs
answered 5 Months ago
zIs
14

The block option decides where the element will be vertically aligned inside the visible area of its scrollable ancestor:

  • Using {block: "start"}, the element is aligned at the top of its ancestor.
  • Using {block: "center"}, the element is aligned at the middle of its ancestor.
  • Using {block: "end"}, the element is aligned at the bottom of its ancestor.
  • Using {block: "nearest"}, the element:
    • is aligned at the top of its ancestor if you're currently below it.
    • is aligned at the bottom of its ancestor if you're currently above it.
    • stays put, if it's already in view.

The inline option decides where the element will be horizontally aligned inside the visible area of its scrollable ancestor:

  • Using {inline: "start"}, the element is aligned at the left of its ancestor.
  • Using {inline: "center"}, the element is aligned at the centre of its ancestor.
  • Using {inline: "end"}, the element is aligned at the right of its ancestor.
  • Using {inline: "nearest"}, the element:
    • is aligned at the left of its ancestor if you're currently on its right.
    • is aligned at the right of its ancestor if you're currently on its left.
    • stays put, if it's already in view.

Both block and inline can be used at the same time to scroll to a specified point in one motion.

Check out the following snippet to see how each works in action.

Snippet:

/* ----- JavaScript ----- */
var buttons = document.querySelectorAll(".btn");

[].forEach.call(buttons, function (button) {
  button.onclick = function () {
    var where = this.dataset.where.split("-");
    document.querySelector("div#a1").scrollIntoView({
      behavior: "smooth",
      block: where[0],
      inline: where[1]
    });
  };
});
/* ----- CSS ----- */
body {
  padding: 500px;
  width: 2000px;
}

header {
  position: fixed;
  top: 0;
  left: 0;
  width: 100;
}

div#a1 {
  width: 1000px;
  height: 300px;
  background: url(//www.w3schools.com/css/trolltunga.jpg);
  background-repeat: no-repeat;
}
<!----- HTML ----->
<header>
  <button class="btn" data-where="start-start">T-L</button>
  <button class="btn" data-where="start-center">T-C</button>
  <button class="btn" data-where="start-end">T-R</button>
  <button class="btn" data-where="center-start">C-L</button>
  <button class="btn" data-where="center-center">C-C</button>
  <button class="btn" data-where="center-end">C-R</button>
  <button class="btn" data-where="end-start">B-L</button>
  <button class="btn" data-where="end-center">B-C</button>
  <button class="btn" data-where="end-end">B-R</button>
</header>

<div id = "a1"></div>
Sunday, August 1, 2021
 
zhartaunik
answered 3 Months ago
18

When i was burning my midnight oil this library came like batman

https://github.com/henrytao-me/smooth-app-bar-layout

According to which the behavior can be improved by following these steps:

1.Change

 android.support.design.widget.AppBarLayout

to

 me.henrytao.smoothappbarlayout.SmoothAppBarLayout and set android:id

2.Remove

 app:layout_behavior="@string/appbar_scrolling_view_behavior"

3.Add header to your NestedScrollView or RecyclerView

Which actually made it to work like charm.

The final setup looks like

                <android.support.design.widget.CoordinatorLayout 
                 xmlns:android="http://schemas.android.com/apk/res/android"
                  xmlns:app="http://schemas.android.com/apk/res-auto"
                  android:layout_width="match_parent"
                  android:layout_height="match_parent">

                  <android.support.v4.widget.NestedScrollView
                    android:id="@+id/nested_scroll_view"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent">

                    <LinearLayout
                      android:layout_width="match_parent"
                      android:layout_height="wrap_content"
                      android:orientation="vertical"
                      android:paddingLeft="16dp"
                      android:paddingRight="16dp"
                      android:paddingTop="@dimen/app_bar_height">

                      <TextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:layout_marginBottom="16dp"
                        android:layout_marginTop="16dp"
                        android:text="@string/text_short" />

                      <TextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:layout_marginBottom="16dp"
                        android:text="@string/text_long" />
                    </LinearLayout>
                  </android.support.v4.widget.NestedScrollView>

                  <me.henrytao.smoothappbarlayout.SmoothAppBarLayout
                    android:id="@+id/smooth_app_bar_layout"
                    android:layout_width="match_parent"
                    android:layout_height="@dimen/app_bar_height">

                    <android.support.design.widget.CollapsingToolbarLayout
                      android:id="@+id/collapsing_toolbar_layout"
                      android:layout_width="match_parent"
                      android:layout_height="match_parent"
                      app:layout_scrollFlags="scroll|exitUntilCollapsed">

                      <android.support.v7.widget.Toolbar
                        android:id="@+id/toolbar"
                        app:layout_collapseMode="pin"
                        app:navigationIcon="@drawable/ic_menu_arrow_back"
                        style="@style/AppStyle.MdToolbar" />
                    </android.support.design.widget.CollapsingToolbarLayout>
                  </me.henrytao.smoothappbarlayout.SmoothAppBarLayout>
                </android.support.design.widget.CoordinatorLayout> 

If you still face any issue while implementing this ask here i would love to help and mark this up if this answer helps.

Wednesday, August 11, 2021
 
user505210
answered 2 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :