Skip to content

Instantly share code, notes, and snippets.

@indikatordesign
Last active February 10, 2026 23:54
Show Gist options
  • Select an option

  • Save indikatordesign/f040ec516ae7ca0ced519bc120fff5a4 to your computer and use it in GitHub Desktop.

Select an option

Save indikatordesign/f040ec516ae7ca0ced519bc120fff5a4 to your computer and use it in GitHub Desktop.
WWL TOC Blog Animation
/* ----------------------------- TOC -------------------------- */
/* Im span wird "^" verwendet, mit einem Icon tauschbar ------- */
/* In delta wird die genaue Zielposition festgelegt ----------- */
/* --------------------------- jQuery ------------------------- */
(function($,w){$(function(){"use strict";
$( '.toc-header' ).wrap( '<div class="toc wrap" />' )
.after( '<span class="toc-item toc-icon header">^</span>' )
$( '#toc-ol li, div.toc.wrap' ).on( 'click', function( e )
{
e.preventDefault()
e.stopPropagation()
let delta = 15,
t = $( this ),
wid = ( $( w ).width() < 981 ? 0 : 90 ) + delta,
elem, index
if ( t.hasClass( 'wrap' ) )
elem = $( '#toc' )
else
{
index = $( '#toc-ol li' ).index( t )
elem = $( '.toc-header' ).eq( index )
}
if ( elem.length < 1 )
return false
$( 'html, body' ).animate({ scrollTop: elem.offset().top - wid }, 200, 'swing', function() { return })
});
});}(jQuery,window));
/* -------------------------- Vanilla JS ---------------------- */
(function( w )
{
w.addEventListener( 'DOMContentLoaded', function()
{
new wwlTOC
} )
}( window ))
class wwlTOC
{
constructor()
{
this.setProperties()
this.initialize()
} // end constructor
setProperties()
{
this.delta = 15
this.duration = 200
this.bound = false
} // end setProperties
initialize()
{
this.wrapHeaders()
this.bindEvents()
} // end initialize
wrapHeaders()
{
const headers = document.querySelectorAll( '.toc-header' )
if ( headers.length < 1 )
return false
headers.forEach( ( header ) =>
{
if ( header.closest( 'div.toc.wrap' ) )
return
const wrap = document.createElement( 'div' )
wrap.className = 'toc wrap'
header.parentNode.insertBefore( wrap, header )
wrap.appendChild( header )
const next = header.nextElementSibling
if ( next && next.classList && next.classList.contains( 'toc-icon' ) )
return
header.insertAdjacentHTML(
'afterend',
'<span class="toc-item toc-icon header">^</span>'
)
} )
} // end wrapHeaders
bindEvents()
{
if ( this.bound )
return false
document.addEventListener( 'click', ( e ) =>
{
this.onClick( e )
} )
this.bound = true
} // end bindEvents
onClick( e )
{
const t = e.target.closest( 'div.toc.wrap, #toc-ol li' )
if ( ! t )
return false
e.preventDefault()
e.stopPropagation()
const wid = ( w.innerWidth < 981 ? 0 : 90 ) + this.delta
let elem = null
if ( t.classList.contains( 'wrap' ) )
elem = document.getElementById( 'toc' )
else
{
const lis = Array.from( document.querySelectorAll( '#toc-ol li' ) )
const index = lis.indexOf( t )
const headers = document.querySelectorAll( '.toc-header' )
if ( index < 0 || ! headers[ index ] )
return false
elem = headers[ index ]
}
if ( ! elem )
return false
const top = elem.getBoundingClientRect().top + ( w.pageYOffset || 0 ) - wid
this.animateScroll( top, this.duration )
} // end onClick
animateScroll( targetY, duration )
{
const startY = w.pageYOffset || document.documentElement.scrollTop || 0
const diff = targetY - startY
if ( diff === 0 )
return false
const now = ( w.performance && typeof w.performance.now === 'function' ) ? () => w.performance.now() : () => Date.now()
const start = now()
const swing = ( t ) => { return 0.5 - Math.cos( Math.PI * t ) / 2 }
const step = ( tNow ) =>
{
const elapsed = tNow - start
const t = Math.min( 1, elapsed / duration )
const eased = swing( t )
w.scrollTo( 0, startY + ( diff * eased ) )
if ( t < 1 )
w.requestAnimationFrame( step )
}
w.requestAnimationFrame( () =>
{
w.requestAnimationFrame( step )
} )
} // end animateScroll
} // end class wwlTOC
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment