56

I have a series of links which are using an anchor mechanism:

<div class="header">
    <p class="menu"><a href="#S1">Section1</a></p>
    <p class="menu"><a href="#S2">Section2</a></p>
    ...
</div>
<div style="width: 100%;">
<a name="S1" class="test">&nbsp;</a>
<div class="curtain">
Lots of text
</div>

<a name="S2" class="test">&nbsp;</a>
<div class="curtain">
lots of text
</div>
...
</div>

I am using the following CSS:

.test
{
    position:relative; 
    margin: 0; 
    padding: 0; 
    float: left;
    display: inline-block;
    margin-top: -100px; /* whatever offset this needs to be */
}

It's working fine. But of course, it's jumping from one section to the next when we click on the link. So I'd like to have a smooth transition, using a scroll of some sort to the start of selected section.

I think I read on Stackoverflow that this is not possible (yet) with CSS3 but I'd like a confirmation and also I'd like to know what 'could' be the solution. I am happy to use JS but I can't use jQuery. I tried to use an on click function on the link, retrieve the "vertical position" of the div that needs to be displayed but I was unsuccessful. I am still learning JS and don't know it well enough to come up with a solution of my own.

Any help/ideas would be greatly appreciated.

user18490
  • 3,546
  • 4
  • 33
  • 52
  • 1
    You are correct - smooth scroll isn't possible with CSS3, since you can't change the scroll position using CSS ergo cannot transition it like you would other properties. You will need to use JS, but without a library like jQuery, implementing smooth scroll might be non-trivial. – BoltClock Jul 29 '14 at 16:42

10 Answers10

52

You can use the scroll-behavior CSS property (which is supported in all browsers except Internet Explorer and Safari):

a {
  display: inline-block;
  padding: 5px 7%;
  text-decoration: none;
}

nav, section {
  display: block;
  margin: 0 auto;
  text-align: center;
}

nav {
  width: 350px;
  padding: 5px;
}

section {
  width: 350px;
  height: 130px;
  overflow-y: scroll;
  border: 1px solid black;
  font-size: 0; 
  scroll-behavior: smooth;    /* <----- THE SECRET ---- */
}

section div{
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  font-size: 8vw;
}
<nav>
  <a href="#page-1">1</a>
  <a href="#page-2">2</a>
  <a href="#page-3">3</a>
</nav>
<section>
  <div id="page-1">1</div>
  <div id="page-2">2</div>
  <div id="page-3">3</div>
</section>
Edric
  • 24,639
  • 13
  • 81
  • 91
vsync
  • 118,978
  • 58
  • 307
  • 400
  • 10
    Your answer is awesome, and hopefully will be the easiest way to go, in the future. However, for anyone wanting to use the above, please note that there is zero compatibility with any version of IE or Safari at this time. The behavior, at least on Safari, is the old behavior of "jumping". – weo3dev Feb 27 '18 at 23:26
  • 1
    as of march 18 it is not supported in safari https://caniuse.com/#search=scroll-behavior – GorvGoyl Mar 26 '18 at 18:34
  • @JerryGoyal - it never seemed to be supposed by *Safari* in the first place, and that link does not provide information regarding *March 18* – vsync Mar 26 '18 at 19:36
  • afaik safari comes under "modern and major browsers" so it's good to mention in the answer. who knows if they add it someday to safari. – GorvGoyl Mar 26 '18 at 19:40
  • 3
    I used it like that *{ scroll-behavior: smooth; } – RafaSashi Oct 10 '18 at 09:22
  • For me, it works only when applied to HTML element. As shown on w3school page: https://www.w3schools.com/cssref/pr_scroll-behavior.asp – mggluscevic Oct 29 '19 at 12:46
  • it is still not supported in many browser in oct-2021 like baidu, safari on IOS, Kali Browser as well IE. so it should not be used. – The EasyLearn Academy Oct 21 '21 at 10:01
36

You can find the answer to your question on the following page:

https://stackoverflow.com/a/17633941/2359161

Here is the JSFiddle that was given:

http://jsfiddle.net/YYPKM/3/

Note the scrolling section at the end of the CSS, specifically:

/*
 *Styling
 */

html,body {
    width: 100%;
    height: 100%;
    position: relative; 
}
body {
overflow: hidden;
}

header {
background: #fff; 
position: fixed; 
left: 0; top: 0; 
width:100%;
height: 3.5rem;
z-index: 10; 
}

nav {
width: 100%;
padding-top: 0.5rem;
}

nav ul {
list-style: none;
width: inherit; 
margin: 0; 
}


ul li:nth-child( 3n + 1), #main .panel:nth-child( 3n + 1) {
background: rgb( 0, 180, 255 );
}

ul li:nth-child( 3n + 2), #main .panel:nth-child( 3n + 2) {
background: rgb( 255, 65, 180 );
}

ul li:nth-child( 3n + 3), #main .panel:nth-child( 3n + 3) {
background: rgb( 0, 255, 180 );
}

ul li {
display: inline-block; 
margin: 0 8px;
margin: 0 0.5rem;
padding: 5px 8px;
padding: 0.3rem 0.5rem;
border-radius: 2px; 
line-height: 1.5;
}

ul li a {
color: #fff;
text-decoration: none;
}

.panel {
width: 100%;
height: 500px;
z-index:0; 
-webkit-transform: translateZ( 0 );
transform: translateZ( 0 );
-webkit-transition: -webkit-transform 0.6s ease-in-out;
transition: transform 0.6s ease-in-out;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;

}

.panel h1 {
font-family: sans-serif;
font-size: 64px;
font-size: 4rem;
color: #fff;
position:relative;
line-height: 200px;
top: 33%;
text-align: center;
margin: 0;
}

/*
 *Scrolling
 */

a[ id= "servicios" ]:target ~ #main article.panel {
-webkit-transform: translateY( 0px);
transform: translateY( 0px );
}

a[ id= "galeria" ]:target ~ #main article.panel {
-webkit-transform: translateY( -500px );
transform: translateY( -500px );
}
a[ id= "contacto" ]:target ~ #main article.panel {
-webkit-transform: translateY( -1000px );
transform: translateY( -1000px );
}
<a id="servicios"></a>
<a id="galeria"></a>
<a id="contacto"></a>
<header class="nav">
<nav>
    <ul>
        <li><a href="#servicios"> Servicios </a> </li>
        <li><a href="#galeria"> Galeria </a> </li>
        <li><a href="#contacto">Contacta  nos </a> </li>
    </ul>
</nav>
</header>

<section id="main">
<article class="panel" id="servicios">
    <h1> Nuestros Servicios</h1>
</article>

<article class="panel" id="galeria">
    <h1> Mustra de nuestro trabajos</h1>
</article>

<article class="panel" id="contacto">
    <h1> Pongamonos en contacto</h1>
</article>
</section>
JustCarty
  • 3,839
  • 5
  • 31
  • 51
Alex Podworny
  • 1,018
  • 1
  • 14
  • 25
  • 1
    Gracias. This implies that you know the height of the div, but that's a perfectly acceptable solution I believe, considering it's short, simple, doesn't require any JS... so it gets my vote. Thx a lot - – user18490 Jul 29 '14 at 17:41
  • can you trigger this on page load instead of an ancor click? – Sagiv b.g Dec 15 '16 at 14:44
  • @Sagivb.g, I just did something to this effect by setting an anchor that linked to itself and having an event click it (eg. a load event or whatever else) where necessary. – Jay Edwards Aug 05 '18 at 00:30
  • what if you don't know the position of the link targets? – Jason S Feb 12 '20 at 16:42
24

Just apply scroll behaviour to all elements with this one-line code:

*{
scroll-behavior: smooth !important;
}
TylerH
  • 20,799
  • 66
  • 75
  • 101
khelifa larbi
  • 241
  • 2
  • 2
  • This worked for me when simply doing `section {scroll-behavior: smooth;}` on sections did not, despite my html being broken up with sections. – Josh Desmond Sep 08 '21 at 15:10
21

While some of the answers were very useful and informative, I thought I would write down the answer I came up with. The answer from Alex was very good, it is however limited in the sense that the height of the div needs to be hard coded in the CSS.

So the solution I came up with uses JS (no jQuery) and is actually a stripped down version (almost to the minimum) of over solutions to solve similar problems I found on Statckoverflow:

HTML

<div class="header">
    <p class="menu"><a href="#S1" onclick="test('S1'); return false;">S1</a></p>
    <p class="menu"><a href="#S2" onclick="test('S2'); return false;">S2</a></p>
    <p class="menu"><a href="#S3" onclick="test('S3'); return false;">S3</a></p>
    <p class="menu"><a href="#S4" onclick="test('S4'); return false;">S3</a></p>
</div>
<div style="width: 100%;">
    <div id="S1" class="curtain">
    blabla
    </div>
    <div id="S2" class="curtain">
    blabla
    </div>
    <div id="S3" class="curtain">
    blabla
    </div>
    <div id="S4" class="curtain">
    blabla
    </div>
 </div>

NOTE THE "RETURN FALSE;" in the on click call. This is important if you want to avoid having your browser jumping to the link itself (and let the effect being managed by your JS).

JS code:

<script>
function scrollTo(to, duration) {
    if (document.body.scrollTop == to) return;
    var diff = to - document.body.scrollTop;
    var scrollStep = Math.PI / (duration / 10);
    var count = 0, currPos;
    start = element.scrollTop;
    scrollInterval = setInterval(function(){
        if (document.body.scrollTop != to) {
            count = count + 1;
            currPos = start + diff * (0.5 - 0.5 * Math.cos(count * scrollStep));
            document.body.scrollTop = currPos;
        }
        else { clearInterval(scrollInterval); }
    },10);
}

function test(elID)
{
    var dest = document.getElementById(elID);
    scrollTo(dest.offsetTop, 500);
}
</script>

It's incredibly simple. It finds the vertical position of the div in the document using its unique ID (in the function test). Then it calls the scrollTo function passing the starting position (document.body.scrollTop) and the destination position (dest.offsetTop). It performs the transition using some sort of ease-inout curve.

Thanks everyone for your help.

Knowing a bit of coding can help you avoiding (sometimes heavy) libraries, and giving you (the programmer) more control.

Em Seven
  • 155
  • 1
  • 1
  • 10
user18490
  • 3,546
  • 4
  • 33
  • 52
  • 2
    i've got to inform you guys that Alex's solution seems to be optimal... i changed and fixed the value to percentage (%) and got the desirable effect. fiddle: http://jsfiddle.net/YYPKM/1676/ it also works fine when the 'panel' class has 100% of height with an absolute position (so to save us time.. here is another version of that fiddle: http://jsfiddle.net/YYPKM/1677/) – ymz Dec 27 '15 at 11:00
  • `test` and `scrollTo` can be merged into one function – Em Seven Feb 22 '16 at 03:38
  • 4
    @ymz - I know I am a little late to the party, but I just wanted to chime in. The reason Alex's solution is sub-par to this one is the fact that Alex's solution is not dynamic. Alex's solution requires you to hardcode the positions in the CSS (whether or not you use %'s, you are still hardcoding). This answer however has created a solution that will not require you to add new CSS rules when you add elements to the page. It just simply works. – stephenkelzer Apr 30 '16 at 04:28
  • 6
    this produces error `element is undefined`. So use `start = window.pageYOffset;` instead. – krivar May 18 '17 at 11:12
  • This method can potentially generate "bounce" issues, but can be resolved like [this](https://stackoverflow.com/a/45547923/3405291) – Megidd Aug 07 '17 at 13:22
  • good solution - thanks! Works in Edge, Chrome and FF once the obvious typos are corrected. – Russ Jackson Dec 05 '17 at 04:43
10

Only mozilla implements a simple property in css : http://caniuse.com/#search=scroll-behavior

you will have to use JS at least.

I personally use this because its easy to use (I use JQ but you can adapt it I guess):

/*Scroll transition to anchor*/
$("a.toscroll").on('click',function(e) {
    var url = e.target.href;
    var hash = url.substring(url.indexOf("#")+1);
    $('html, body').animate({
        scrollTop: $('#'+hash).offset().top
    }, 500);
    return false;
});

just add class toscroll to your a tag

Aditya Wagh
  • 182
  • 1
  • 2
  • 14
Reign.85
  • 2,420
  • 1
  • 28
  • 28
4

If anybody is just like me willing to use jQuery, but still found himself looking to this question then this may help you guys:

https://html-online.com/articles/animated-scroll-anchorid-function-jquery/

$(document).ready(function () {
            $("a.scrollLink").click(function (event) {
                event.preventDefault();
                $("html, body").animate({ scrollTop: $($(this).attr("href")).offset().top }, 500);
            });
        });
<a href="#anchor1" class="scrollLink">Scroll to anchor 1</a>
<a href="#anchor2" class="scrollLink">Scroll to anchor 2</a>
<p id="anchor1"><strong>Anchor 1</strong> - Lorem ipsum dolor sit amet, nonumes voluptatum mel ea.</p>
<p id="anchor2"><strong>Anchor 2</strong> - Ex ignota epicurei quo, his ex doctus delenit fabellas.</p>
Kalin Krastev
  • 552
  • 6
  • 19
1

I implemented the answer suggested by @user18490 but ran into two problems:

  • First bouncing when user clicks on several tabs/links multiple times in short succession
  • Second, the undefined error mentioned by @krivar

I developed the following class to get around the mentioned problems, and it works fine:

export class SScroll{
    constructor(){
        this.delay=501      //ms
        this.duration=500   //ms
        this.lastClick=0
    }

    lastClick
    delay
    duration

    scrollTo=(destID)=>{
        /* To prevent "bounce" */
        /* https://stackoverflow.com/a/28610565/3405291 */
        if(this.lastClick>=(Date.now()-this.delay)){return}
        this.lastClick=Date.now()

        const dest=document.getElementById(destID)
        const to=dest.offsetTop
        if(document.body.scrollTop==to){return}
        const diff=to-document.body.scrollTop
        const scrollStep=Math.PI / (this.duration/10)
        let count=0
        let currPos
        const start=window.pageYOffset
        const scrollInterval=setInterval(()=>{
            if(document.body.scrollTop!=to){
                count++
                currPos=start+diff*(.5-.5*Math.cos(count*scrollStep))
                document.body.scrollTop=currPos
            }else{clearInterval(scrollInterval)}
        },10)
    }
}

UPDATE

There is a problem with Firefox as mentioned here. Therefore, to make it work on Firefox, I implemented the following code. It works fine on Chromium-based browsers and also Firefox.

export class SScroll{
    constructor(){
        this.delay=501      //ms
        this.duration=500   //ms
        this.lastClick=0
    }
    lastClick
    delay
    duration
    scrollTo=(destID)=>{
        /* To prevent "bounce" */
        /* https://stackoverflow.com/a/28610565/3405291 */
        if(this.lastClick>=(Date.now()-this.delay)){return}

        this.lastClick=Date.now()
        const dest=document.getElementById(destID)
        const to=dest.offsetTop
        if((document.body.scrollTop || document.documentElement.scrollTop || 0)==to){return}

        const diff=to-(document.body.scrollTop || document.documentElement.scrollTop || 0)
        const scrollStep=Math.PI / (this.duration/10)
        let count=0
        let currPos
        const start=window.pageYOffset
        const scrollInterval=setInterval(()=>{
            if((document.body.scrollTop || document.documentElement.scrollTop || 0)!=to){
                count++
                currPos=start+diff*(.5-.5*Math.cos(count*scrollStep))
                /* https://stackoverflow.com/q/28633221/3405291 */
                /* To support both Chromium-based and Firefox */
                document.body.scrollTop=currPos
                document.documentElement.scrollTop=currPos
            }else{clearInterval(scrollInterval)}
        },10)
    }
}
Megidd
  • 7,089
  • 6
  • 65
  • 142
  • Hi, can you elaborate some that how I can use this in HTML, as above @user18490 shows that onclick can do this, but as you have wrote a whole class, so how can I call it using HTML. Thanks – Muhammad Noman Aug 13 '17 at 15:34
  • @MuhammadNoman Well, actually I'm using this class with `ReactJS` and `MobX`. But I feel like it might be pretty straightforward to just convert `scrollTo` method in the class to a separate function and call it as @user18490 has called – Megidd Aug 15 '17 at 11:40
  • 1
    I couldn't use your class method, and after converting into a function encountered the bounce error again. What I did was remove the `delay` and `lastClick` variables, made `scrollInterval` global and then just called `clearInterval(scrollInterval)` directly before calling setInterval. Problem solved. – Mordred Oct 29 '18 at 22:01
0

I guess it might be possible to set some kind of hardcore transition to the top style of a #container div to move your entire page in the desired direction when clicking your anchor. Something like adding a class that has top:-2000px.

I did use JQuery because I'm to lazy too use native JS, but it is not necessary for what I did.

This is probably not the best possible solution because the top content just moves towards the top and you can't get it back easily, you should definitely use JQuery if you really need that scroll animation.

DEMO

Sir Celsius
  • 822
  • 8
  • 22
0

Here is a pure css solution using viewport units and variables that automatically scales to the device (and works on window resize). I added the following to Alex's solution:

        html,body {
            width: 100%;
            height: 100%;
            position: fixed;/* prevents scrolling */
            --innerheight: 100vh;/* variable 100% of viewport height */
        }

        body {
            overflow: hidden; /* prevents scrolling */
        }

        .panel {
            width: 100%;
            height: var(--innerheight); /* viewport height */

        a[ id= "galeria" ]:target ~ #main article.panel {
            -webkit-transform: translateY( calc(-1*var(--innerheight)) );
            transform: translateY( calc(-1*var(--innerheight)) );
        }

        a[ id= "contacto" ]:target ~ #main article.panel {
            -webkit-transform: translateY( calc(-2*var(--innerheight)) );
            transform: translateY( calc(-2*var(--innerheight)) );
0

I tried user18490 solution but there were some problems like:

  • Bouncing when clicked more than once
  • Bouncing if there isn't sufficient space below the target elements
  • Element is not defined
  • Problem of parent Element
  • e.t.c

Well after I edited and researched, I was able to come up with a solution. Hopefully it'll work for everyone

Just change the script tag to:

var html = document.documentElement

var body = document.body

var documentHeight = Math.max(body.scrollHeight, body.offsetHeight, html.scrollHeight, html.clientHeight, html.offsetHeight)

var PageHeight = Math.max(html.clientHeight || 0, window.innerHeight || 0)

function scrollDownTo(to, duration) {
    if (document.body.scrollTop == to) return;
    if ((documentHeight-to) < PageHeight) {
        to = documentHeight - PageHeight;
    }
    var diff = to - window.pageYOffset;
    var scrollStep = Math.PI / (duration / 10);
    var count = 0, currPos; ajaxe = 1
    var start = window.pageYOffset;
    var scrollInterval = setInterval(function(){
        if (window.pageYOffset != to) {
            count = count + 1;
            if (ajaxe > count) {
                clearInterval(scrollInterval)
            }
            currPos = start +  diff * (0.5 - 0.5 * Math.cos(count * scrollStep));
            scroll( 0, currPos)
            ajaxe = count
        }
        else { clearInterval(scrollInterval);}
    },20);
    }

function test (elID) {
    var dest = document.getElementById(elID);
    scrollDownTo((dest.getBoundingClientRect().top + window.pageYOffset), 500);
}

The HTML is still the same:

<div class="header">
    <p class="menu"><a href="#S1" onclick="test('S1'); return false;">S1</a></p>
    <p class="menu"><a href="#S2" onclick="test('S2'); return false;">S2</a></p>
    <p class="menu"><a href="#S3" onclick="test('S3'); return false;">S3</a></p>
    <p class="menu"><a href="#S4" onclick="test('S4'); return false;">S3</a></p>
</div>
<div style="width: 100%;">
    <div id="S1" class="curtain">
    blabla
    </div>
    <div id="S2" class="curtain">
    blabla
    </div>
    <div id="S3" class="curtain">
    blabla
    </div>
    <div id="S4" class="curtain">
    blabla
    </div>
 </div>

If you still encounter any issues kindly comment

Elpis
  • 79
  • 6