Although presentations are usually created using native apps, we decided to challenge ourselves and design a presentation template for the browser. It wasn’t an easy task, for the way you interact with a presentation slideshow is different compared to how you scroll through a website: each unit/slide must be isolated, so that its content can be assimilated before switching to the next one.
How do you navigate through a presentation? The easiest way is to use keyboard arrow keys (for devices that have them). However we had to take into account that all other interactions (click, scroll…) had to work as well when building something for the web.
Let us know what you think in the comments section ;)
👋 A new version of this component is available. Download now →.
Creating the structure
The HTML structure is composed by two main elements: a <nav>
for the slideshow navigation, and an ordered list for the slideshow items. We used a nested ordered list for items with multiple sub slides.
<div class="cd-slideshow-wrapper">
<nav class="cd-slideshow-nav">
<button class="cd-nav-trigger">
Open Navigation
<span aria-hidden="true"></span>
</button>
<div class="cd-nav-items">
<ol>
<li><a href="#slide-1">Slide 1</a></li>
<li>
<a href="#slide-2">Slide 2</a>
<ol class="sub-nav">
<li><a href="#slide-2">Slide 2 - Sub 1</a></li>
<!-- other sub-slide links here -->
</ol>
</li>
<li><a href="#slide-3">Slide 3</a></li>
<!-- other slide links here -->
</ol>
</div> <!-- .cd-nav-items -->
</nav> <!-- .cd-slideshow-nav -->
<ol class="cd-slideshow">
<li class="visible" id="slide-1">
<div class="cd-slider-content">
<div class="content-wrapper">
<h2>Presentation Slideshow</h2>
<p>A simple presentation template in CSS & jQuery.</p>
</div>
</div>
</li>
<li id="slide-2">
<ol class="sub-slides">
<li>
<div class="cd-slider-content">
<div class="content-wrapper">
<h2>Slider #2</h2>
</div>
</div>
</li>
<!-- sub-slides content here -->
</ol> <!-- .sub-slides -->
</li>
<!-- additional slides here -->
</ol> <!-- .cd-slideshow -->
</div> <!-- .cd-slideshow-wrapper -->
Adding style
On small devices (viewport width smaller than 1100px), we organized all slides as a simple list. For items with sub slides, though, we implemented a slider with drag/swipe interaction to navigate it.
.cd-slideshow .sub-slides {
width: 100%;
transition: transform 0.3s;
}
.cd-slideshow > li,
.cd-slideshow .sub-slides > li {
position: relative;
z-index: 1;
height: 100vh;
width: 100vw;
}
.cd-slideshow .sub-slides > li {
float: left;
}
The width of the ordered list items with sub slides is set using JavaScript. When a user navigates from a sub slide to the previous/next one, we translate the .sub-slides
element (nested ordered list) along the X axis (more in the Events handling section).
On bigger devices, we set the .cd-slideshow-wrapper
height to 100vh and add an overflow hidden, so that only the slide in the viewport is visible. We then set height, width and margin of the .cd-slider-content
to center it in the viewport.
The .visible
class is added to the visible slide: it is used to hide the .cd-slider-content::after
pseudo-element (used to change the slide background color when the slide is out of focus) and show the slide content.
@media only screen and (min-width: 1100px) {
.cd-slideshow-wrapper {
height: 100vh;
overflow: hidden;
}
.cd-slideshow {
transition: transform 0.6s;
}
.cd-slideshow > li, .cd-slideshow .sub-slides > li {
height: auto;
width: auto;
}
.cd-slider-content {
height: 84vh;
width: 90vw;
margin: 2vh 5vw;
border-radius: 10px;
cursor: pointer;
}
.visible .sub-visible .cd-slider-content,
.visible > .cd-slider-content {
/* visible slide */
cursor: auto;
}
.cd-slideshow > li:first-of-type .cd-slider-content {
margin-top: 8vh;
}
.sub-slides > li:first-of-type .cd-slider-content {
margin-left: 5vw;
}
.sub-slides > li .cd-slider-content {
margin-left: 1.25vw;
margin-right: 1.25vw;
}
.cd-slider-content .content-wrapper {
height: 100%;
/* hide the slide content if the slide is not selected/visible */
opacity: 0;
box-shadow: 0 6px 40px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.15);
border-radius: inherit;
transition: opacity 0.6s;
}
.cd-slider-content::after {
/* this is used to change the slide background color when the slide is out of focus */
content: '';
position: absolute;
z-index: 3;
top: 0;
left: 0;
height: 100%;
width: 100%;
border-radius: inherit;
background-color: #3a3a3a;
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.1);
opacity: 1;
visibility: visible;
transition: opacity 0.6s, visibility 0.6s;
}
.visible .cd-slider-content .content-wrapper {
opacity: 1;
}
.visible .cd-slider-content::after {
opacity: 0;
visibility: hidden;
}
}
When a user navigates from a slide to the next/previous one, we translate the .cd-slideshow
element along the Y axis (more in the Events handling section).
Events handling
The presentation can be navigated in different ways: using the keyboard; using the slideshow main navigation; clicking on slides out of focus or scrolling through the slides.
Two main functions have been used to implement the slideshow navigation: the updateSlide
to navigate from a slide to the next/previous one, and the updateSubSlide
to navigate from a sub slide to the next/previous one. For the updateSubSlide
function, for example, we used:
function updateSubSlide(listItem, string, subSlide) {
var translate,
marginSlide = Number(listItem.find('.cd-slider-content').eq(0).css('margin-right').replace('px', ''))*6,
windowWidth = window.innerWidth;
windowWidth = ( mq == 'desktop' ) ? windowWidth - marginSlide : windowWidth;
if( listItem.children('.sub-slides').length > 0 ) {
var subSlidesWrapper = listItem.children('.sub-slides'),
visibleSubSlide = subSlidesWrapper.children('.sub-visible');
if( string == 'nav' ) {
/* we have choosen a new slide from the navigation */
var newSubSlide = subSlide;
} else {
var newSubSlide = (string == 'next') ? visibleSubSlide.next() : visibleSubSlide.prev();
}
var newSubSlidePosition = newSubSlide.index();
translate = parseInt(- newSubSlidePosition*windowWidth);
setTransformValue(subSlidesWrapper.get(0), 'translateX', translate + 'px');
visibleSubSlide.removeClass('sub-visible');
newSubSlide.addClass('sub-visible');
}
}
function setTransformValue(element, property, value) {
element.style["-webkit-transform"] = property+"("+value+")";
element.style["-moz-transform"] = property+"("+value+")";
element.style["-ms-transform"] = property+"("+value+")";
element.style["-o-transform"] = property+"("+value+")";
element.style["transform"] = property+"("+value+")";
// ...
}