Smooth Scroll “Back to Top” Button

I have been repeatedly asked in the comments to explain how the "Back to Top" arrow is implemented on this site. I'm sharing it now.

Some advantages:

  • Works in all browsers;
  • Disappears at the top of the screen;
  • Can be added to any website, not just WordPress.

We will get a fixed-position button that, when clicked, smoothly scrolls the screen to the top and the arrow disappears at the top.

Below are two options: using pure JS and using jQuery. The button code is suitable for any engine, not just WordPress - it could be Drupal, Bitrix, Joomla, DLE, or UCOZ.

Option 1: Native JS

document.addEventListener( 'DOMContentLoaded', function(){

	let div = document.createElement( 'div' )
	div.innerHTML = `<button role="button" aria-label="Scroll Top" class="scroll-top-btn"></button>`.trim()
	let scrollTop = div.firstChild

	document.body.append( scrollTop )

	scrollTop.addEventListener( 'click', ev => {
		window.scrollTo( 0, 0 )
	} )

	let showHideButton = function(){
		window.scrollY > 300
			? scrollTop.classList.add( '--visible' )
			: scrollTop.classList.remove( '--visible' )
	}

	window.addEventListener( 'scroll', showHideButton )
	showHideButton()

} );

Smooth scrolling is achieved with the following styles:

html{
	scroll-behavior: smooth;
	scroll-padding-top: calc( var(--header-height) + 1rem );
}

SCSS Styles

// scroll-top ---

.scroll-top-btn{
	font-size: 150%; // button size

	padding:0; border:0; // reset button
	display: block;
	overflow: hidden; width: 0; height: 0;
	z-index: 99; position: fixed;
	bottom: 2rem; right: 2rem;
	line-height: 0;

	border-radius: var(--border-radius);
	box-shadow: 0 3px 30px -5px rgb(0 0 0 / 30%);

	background-color: var(--white);
	background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='%23999' aria-hidden='true'%3E%3Cpath fill-rule='evenodd' d='M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z' clip-rule='evenodd'%3E%3C/path%3E%3C/svg%3E");
	background-repeat: no-repeat;
	background-size: 70%;
	background-position: center 50%;

	opacity: .6;
	transition: all 150ms;
	cursor:pointer;

	@include layoutBreak {
		display: none;
	}

	&.--visible { overflow: visible; width: 2em; height: 2em; }

	&:hover{
		opacity: 1;
	}
}

Option 1.2: Native JS (with smooth scroll function)

document.addEventListener( 'DOMContentLoaded', function(){

	// create DOM element
	const scrollTopHTML = `<button role="button" area-lable="Scroll Top" class="scroll-top-btn"></button>`
	const div = document.createElement( 'div' )
	div.innerHTML = scrollTopHTML.trim()
	const scrollTop = div.firstChild
	document.body.append( scrollTop )

	// init

	scrollTop.addEventListener( 'click', button_click )

	window.addEventListener( 'scroll', showHideButton )
	showHideButton()

	// methods

	function button_click( ev ) {
		ev.preventDefault();

		scroll_to( {
			element: document.documentElement,
			goto: 0,
			speed: 2000,
			frames: 10,
			easing_func: 'easeOutCirc',
		} )

	}

	function showHideButton() {
		window.scrollY > 300
			? scrollTop.classList.add( '--visible' )
			: scrollTop.classList.remove( '--visible' )
	}

	function scroll_to( args ) {

		/**
		 * t = Time - Amount of time that has passed since the beginning of the animation.
		 *     Usually starts at 0 and is slowly increased using a game loop or other update function.
		 * b = Beginning value - The starting point of the animation.
		 *     Usually it's a static value, you can start at 0 for example.
		 * c = Change in value - The amount of change needed to go from starting point to end point.
		 *     It's also usually a static value.
		 * d = Duration - Amount of time the animation will take.
		 *     Usually a static value aswell.
		 *
		 * @see https://spicyyoghurt.com/tools/easing-functions
		 * @see https://easings.net/
		 */
		const easing_funcs = {

			easeLinear: ( t, b, c, d ) => {
				return c * t / d + b
			},
			easeInSine: ( t, b, c, d ) => {
				return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;
			},
			easeOutSine: ( t, b, c, d ) => {
				return c * Math.sin(t / d * (Math.PI / 2)) + b;
			},
			easeInCubic: ( t, b, c, d ) => {
				return c * (t /= d) * t * t + b;
			},
			easeOutCubic: ( t, b, c, d ) => {
				return -c * (t /= d) * (t - 2) + b
			},
			easeInExpo: ( t, b, c, d ) => {
				return (t === 0) ? b : c * Math.pow(2, 10 * (t / d - 1)) + b;
			},
			easeOutExpo: ( t, b, c, d ) => {
				return ( t === d ) ? b + c : c * ( -Math.pow( 2, -10 * t / d ) + 1 ) + b
			},
			easeOutCirc: ( t, b, c, d ) => {
				return c * Math.sqrt(1 - (t = t / d - 1) * t) + b
			},

		}

		const el = args.element || document.documentElement
		const speed = args.speed
		const frames = args.frames || 10
		const goto_pos = args.goto
		const easing_func = args.easing_func || 'easeLinear'
		const anim_end_func = args.anim_end || null
		const anim_start_func = args.anim_start || null

		const start_pos = el.scrollTop
		const start_time = Date.now()
		let tuntime_frames = Math.round( speed / frames )

		const recursive = () => {

			let left_time = ( Date.now() - start_time )
			let anim_tick = left_time / tuntime_frames--
			let anim_step = Math.min( left_time / speed, 1 ) // max 1

			el.scrollTop = easing_funcs[ easing_func ]( anim_step, start_pos, (goto_pos - start_pos), 1 )

			if( anim_step !== 1 ){
				// next anim step
				this._scroll_to_tick_tm = setTimeout( recursive, anim_tick )
			}
			// done
			else {
				delete this._scroll_to_tick_tm
				// add a delay for sure
				anim_end_func ? setTimeout( anim_end_func, 20 ) : null
			}

		}

		// RUN if already not in process
		if( ! this._scroll_to_tick_tm ){

			anim_start_func && anim_start_func()

			recursive()
		}

	}

} );

Use the button styles from example 1.

Option 2: "Back to Top" Button in Pure JavaScript

View DEMO in pure javascript >>>

If jQuery is not present on the site, there is no need to include a fairly large framework just to install such a button.

To implement a smoothly scrolling "back to top" button without jQuery, use the following js code:

/**
 * Scrolling script: scrollTop button
 * button css class: scrollTop
 */

window.top = {};
var sc = window.top;

sc.time = 12; // cranking time

sc.goTop = function (time, acceleration) {
	acceleration = acceleration || 0.1;
	time = time || sc.time;

	var dx = 0;
	var dy = 0;
	var bx = 0;
	var by = 0;
	var wx = 0;
	var wy = 0;

	if (document.documentElement) {
		dx = document.documentElement.scrollLeft || 0;
		dy = document.documentElement.scrollTop || 0;
	}
	if (document.body) {
		bx = document.body.scrollLeft || 0;
		by = document.body.scrollTop || 0;
	}
	var wx = window.scrollX || 0;
	var wy = window.scrollY || 0;

	var x = Math.max(wx, Math.max(bx, dx));
	var y = Math.max(wy, Math.max(by, dy));

	var speed = 1 + acceleration;
	window.scrollTo(Math.floor(x / speed), Math.floor(y / speed));
	if(x > 0 || y > 0) {
		var invokeFunction = "window.top.goTop("+ time +")"
		window.setTimeout(invokeFunction, time);
	}
	return false;
}

sc.showHide = function (){
	var a = document.getElementById('gotop');

	if( ! a ){
		// If there is no item, add it
		var a = document.createElement('a');
		a.id = "gotop";
		a.className = "scrollTop";
		a.href = "#";
		a.style.display = "none";
		a.style.position = "fixed";
		a.style.zIndex = "9999";
		a.onclick = function(e){ e.preventDefault(); window.top.goTop(); }
		document.body.appendChild(a);
	}

	var stop = (document.body.scrollTop || document.documentElement.scrollTop);
	if( stop > 300 ){
		a.style.display = 'block';
		sc.smoothopaque(a, 'show', 30, false);
	} else {
		sc.smoothopaque(a, 'hide', 30, function(){a.style.display = 'none';});
	}

	return false;
}

// Smooth change of transparency
sc.smoothopaque = function (el, todo, speed, endFunc){
	var
	startop = Math.round( el.style.opacity * 100 ),
	op = startop,
	endop = (todo == 'show') ? 100 : 0;

	clearTimeout( window['top'].timeout );

	window['top'].timeout = setTimeout(slowopacity, 30);

	function slowopacity(){
		if( startop < endop ){
			op += 5;
			if( op < endop )
				window['top'].timeout = setTimeout(slowopacity, speed);
			else
				(endFunc) && endFunc();
		}
		else {
			op -= 5;
			if( op > endop ){
				window['top'].timeout = setTimeout(slowopacity, speed);
			}
			else
				(endFunc) && endFunc();
		}

		// opacity
		el.style.opacity = (op/100);
		el.style.filter = 'alpha(opacity=' + op + ')';
	}
}

if (window.addEventListener){
	window.addEventListener("scroll", sc.showHide, false);
	window.addEventListener("load", sc.showHide, false);
}
else if (window.attachEvent){
	window.attachEvent("onscroll", sc.showHide);
	window.attachEvent("onload", sc.showHide);
}

The CSS styles should be specified separately in the style file:

.scrollTop{
	background:url('path_to_file.png') 0 0 no-repeat;
	width:50px; height:70px;
	bottom:10px; left:48%;
}
.scrollTop:hover{ background-position:0 -76px; }

Option 3: "Back to Top" Buttons in jQuery

DEMO: See how it looks >>>

1. Connect jQuery

First, we need to connect jQuery, if it is not already installed on your site (this rarely happens). To do this, copy the following code into the theme file functions.php:

add_action('wp_enqueue_scripts', 'my_scripts_method' );
function my_scripts_method(){
	wp_enqueue_script( 'jquery' );
}

If you already have jQuery connected, you can skip this step.

2. Embed the .js code

The next step is to embed the code for the button itself. To do this, create a file my_topbutton.js and insert the following code into it. Then you need to connect this file to the page.

Alternatively, to avoid dealing with a file, you can simply copy this code into an existing .js file.

This code should be connected after the jQuery library code.

jQuery(document).ready(function($){

	/**
	 * jQuery Back to Top Button.
	 * Author: Timur Kamaev - wp-kama.ru
	 * version 2.2
	 */
	$('body').append('<style>\
		.scrollTop{\
			display:none; z-index:9999; position:fixed; bottom:10px; left:48%; width:50px; height:70px;\
			background:url(http://example.com/path_to_file.png) 0 0 no-repeat;\
		}\
		.scrollTop:hover{ background-position:0 -76px;}\
	</style>');

	var speed      = 500,
		$scrollTop = $('<a href="#" class="scrollTop"></a>').appendTo('body');

	$scrollTop.click(function(e){
		e.preventDefault();

		$( 'html:not(:animated),body:not(:animated)' ).animate({ scrollTop: 0}, speed );
	});

	// appearance
	function show_scrollTop(){
		( $(window).scrollTop() > 300 ) ? $scrollTop.stop().fadeIn(600) : $scrollTop.stop().fadeOut(600);
	}
	$(window).scroll( function(){ show_scrollTop(); } );
	show_scrollTop();

});
3. Adjust CSS styles

In the embedded JS code, there are lines with CSS styles:

$('body').append('<style>\
	.scrollTop{\
		display:none; z-index:9999; position:fixed; bottom:10px; left:48%; width:50px; height:70px;\
		background:url(http://example.com/path_to_file.png) 0 0 no-repeat;\
	}\
	.scrollTop:hover{ background-position:0 -76px;}\
</style>');

Instead of http://example.com/path_to_file.png, you need to specify the path to the image file with the arrow image. The image needs to be uploaded to the site.

You also need to adjust the sizes for the image: width:50px; height:70px; and background-position:0 -76px;. These sizes are calculated for an image of 50x140 pixels:

arrows1

If you have a different image, in the parameter width:50px; specify the width of the image; in the parameter height:70px; specify half the height (the height of the top part of the image); in the parameter background-position:0 -76px; - how many pixels to move from the top of the image when hovering over the "button". For this image, it's -76px.

That's it, all set!

So, in three steps, you can install the "back to top" button, which, when clicked, will smoothly scroll the user back to the top of the page.

#3.2 CSS styles without using an image - even simpler!

Due to the latest trends in simplicity, you can completely avoid using an image and simply style the button using CSS styles, here's an example:

$('body').append('<style>\
	.scrollTop{\
		display:none; z-index:9999; position:fixed; bottom:0; padding:1em 2em; left:48%; border-radius:.2em;\
		background:rgba(0,0,0,0.2); color:#fff;\
	}\
	.scrollTop:hover{ background:rgba(0,0,0,0.6); color:#fff; text-decoration:none; }\
	.scrollTop:before{ content:"▴"; /* ▲ ▴ △ ▵ ⏫ ⏶ ⧋ */ }\
</style>');

Replace the similar lines in the script code with these, and you will get a button like this:

naverh-knopka-dlya-sajta

Images for Buttons (for options 2 and 3)

I gathered several images that you might like.

arrows1 arrows15 arrows18 arrows14 arrows12 arrows7 arrows13 arrows3 arrows4 arrows6 arrows5 arrows17 arrows8 arrows10 arrows11 arrows16 arrows2 arrows9

Copy the image to your site and then adjust the CSS styles for the copied image, like this:

.scrollTop{
	background:url('path_to_file.png') 0 0 no-repeat;
	width:50px; /* adjust: image width */
	height:70px; /* adjust: button height - half of the image height */
	bottom:10px;
	left:48%;
}
.scrollTop:hover{
	/* adjust: offset on hover,
	needs to be adjusted manually. Approximately equal to the button height */
	background-position:0 -76px;
}