Scrolling layout example
Scrolling layout example

SVELTE

How to build a reusable layout component in Svelte

In a few lines, you can have a component that you will actually use across your pages

Andrea Dalle Palle
Sep 18 · 4 min read

In this tutorial, we will implement a Svelte component that serves as a structure for our project’s pages.

It comes in handy when our pages have similar layouts and we want to react to content scroll.

The structure we will build here is composed by:

  • main content: scrollable
  • header: fixed on top of the page
  • footer: fixed on bottom of the page

We can obviously adapt the component to our needs. You can skip to the end if you only want the complete component code.

Adding header and footer

We start adding the main content layout. This is composed by a “main” element and a slot.

<main bind:this={main} class:header class:footer>
<slot/>
</main>

Next to it, we will add the containers that will host header and footer: we kept the main slot for the content so we now need to add two named slots (“header” and “footer”).

<div class="slotHeader">
<slot name="header"/>
</div>
<div class="slotFooter">
<slot name="footer"/>
</div>

In the “script” tag, we now declare :

  • the “main” variable, which will be binded to the “main” element
  • the necessary attributes “header” and “footer”, which indicate if they are present
  • and their height, default to 64
<script>
let main;
export let header = false;
export let headerHeight = 64;
export let footer = false;
export let footerHeight = 64;
</script>

We also react to attribute changes to set the correct CSS variables for heights.

$: if(main) {
main.style.setProperty('--header-height', headerHeight + 'px');
main.style.setProperty('--footer-height', footerHeight + 'px');
}

Now, in “style” tag, we pad the content according to the attributes and fix header and footer in page.

<style>
main {
height: 100%;
overflow-y: auto;
}
main.header {
padding-top: var(--header-height, 0px);
}
main.footer {
padding-bottom: var(--footer-height, 0px);
}
.slotHeader {
position: fixed;
top: 0px;
left: 0px;
width: 100%;
}
.slotFooter {
position: fixed;
bottom: 0px;
left: 0px;
width: 100%;
}
</style>

Complete code

<main bind:this={main} class:header class:footer>
<slot/>
</main>
<div class="slotHeader">
<slot name="header"/>
</div>
<div class="slotFooter">
<slot name="footer"/>
</div>
<script>
let main;
export let header = false;
export let headerHeight = 64;
export let footer = false;
export let footerHeight = 64;
$: if(main) {
main.style.setProperty('--header-height', headerHeight + 'px');
main.style.setProperty('--footer-height', footerHeight + 'px');
}
</script>
<style>
main {
height: 100%;
overflow-y: auto;
}
main.header {
padding-top: var(--header-height, 0px);
}
main.footer {
padding-bottom: var(--footer-height, 0px);
}
.slotHeader {
position: fixed;
top: 0px;
left: 0px;
width: 100%;
}
.slotFooter {
position: fixed;
bottom: 0px;
left: 0px;
width: 100%;
}
</style>

Listen to scroll

Let’s say that we want the header to hide and show as the user scrolls the content, as in Material Design’s App Bars.

To implement this, we have to listen to main content scroll event and calculate if and when the header will hide. The same is valid for footer.

In layout, edit the “main” element.

<main bind:this={main} class:header class:footer on:scroll={Scroll}>

And the header and footer containers.

<div class="slotHeader" class:hide={hideHeader && scroller.hide}>
<slot name="header"/>
</div>
<div class="slotFooter" class:hide={hideFooter && scroller.hide}>
<slot name="footer"/>
</div>

In “script” tag, we must declare a few new variables:

  • the scrolling data
  • the threshold, after which to hide header and footer
  • and whether the header and footer can hide on scroll
let scroller = {
scroll: 0,
direction: true, // true: scroll down; false: scroll up
delta: 0,
hide: false,
initialScroll: 0
};
export let threshold = 64;
export let hideHeader = false;
export let hideFooter = false;

Then, we add the “Scroll” function used to calculate the current values.

const Scroll = e => {
const scroll = e.target.scrollTop;

scroller = {
scroll,
direction: scroll > scroller.scroll,
delta: scroll - scroller.initialScroll,
hide: scroll > threshold && scroller.hide
? scroll - scroller.initialScroll > -threshold
: scroll - scroller.initialScroll > threshold,
initialScroll: (scroll > scroller.scroll) != scroller.direction
? scroll
: scroller.initialScroll
};
};

To conclude, we edit the “style” tag to animate the header and footer on hide.

.slotHeader {
// previous code
transition: all .2s ease-in-out;
}
.slotHeader.hide {
transform: translateY(-100%);
}
.slotFooter {
// previous code
transition: all .2s ease-in-out;
}
.slotFooter.hide {
transform: translateY(100%);
}

Use the scroller in the parent

Exposing the scroller object to the parent, we can use it to add a shadow on header element on scroll, or to make other page elements to show or hide.

We can do it in three ways, I suggest you to implement all of them.

export let scroller = {...};

In this way you can bind:scroller on your parent.

<main bind:this={main} class:header class:footer on:scroll={Scroll}>
<slot {scroller}/>
</main>

In this way, you can let:scroller in your parent and use it inside the Layout component.

import {createEventDispatcher} from "svelte";const dispatch = createEventDispatcher();const Scroll = e => {
// previous code
dispatch('scroller', scroller);
};

In this way, you can add on:scroller event to parent and maybe set to a variable.

Conclusion

Feel free to write a comment if you have any question or suggestion.

Thank you all, and follow me for more articles!

JavaScript In Plain English

New JavaScript + Web Development articles every day.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store