Have you ever wanted to show your friends media from holidays? How cubersome it was to mix photos and videos? Enough of frame transition? Dreaming about a fully customisable presentation experience? Presentation file size huge? This HTML based presenter will let you show your contents just the way you desire. Either launch it and drag the files in or fully define all the properties.
What is SlideRshow and what advantages it has?
- media player
- organizer
- Simply tag photos as you browse them to be regrouped for a screening.
- presentation software
- Ridiculously small file size – given you do not have to keep copies of media files as in other presentation software. Just link local or online files! Have you ever tried to put 1 GB of images onto slides?
- Super easy video trimming.
The application runs in the browser – see the SlideRshow right now. Or download the repository and open the local file. Or add somewhere a tag and that is all!
<script src="https://cdn.jsdelivr.net/gh/CZ-NIC/slidershow@latest/slidershow/slidershow.js"></script>
When in the application, drag and drop media files into the page and just start the playthrough. (Remember: Nothing is uploaded to the server. Check the code – there is no server.) Should a more detailed presentation be needed, just export the presentation HTML file with Ctrl+E and edit it at will.
What can you acheive? See a variety of features in another example at extra/tutorial.html.
- Prologue
- Usage
- Contents
- Playback
- Structure
- License
There is a varienty of keyboard shortcuts. Click the menu button in the top right corner (Esc) or hit F1 to see a complete list.
- Next frame: Right, PageDown, n, Space
- Previous frame: Left, PageUp, p
- Video: Adjust speed by Numpad +/-
- Toggle file info: f
- Toggle HUD map: m
The <menu>
is displayed before the presentation starts, unless the <main>
has the data-start
attribute.
While presenting, press Alt+J to display the thumbnail ribbon or Alt+G to see full grid. There, you can easily sort the frames. Either move them one by one or sort whole section (by EXIF date or file names). Import new images just by dragging them in.
While presenting, you may appraise an auxiliary window on the second monitor that shows you the next frame and presenting notes. Start it with Alt+W.
Start tagging mode with Alt+T. Use Numpad to tag the images – think of a tag as a number that corresponds to one of your categories.. Then in the menu, hit Alt+Shift+G to group the images to the <section>
according to tags. Export with Ctrl+S. Sorted & ready!
Note that the tag is stored in the browser (local storage)[https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage] by the file name so that you do not lose the information at crash. In case you import another photo with the same name, it will inherit the tag from the clashing file.
Put the presentation content to the <main>
tag which contain <article>
tags (~ frames).
To control the presentation flow, we use many attributes. These are resolved in the following way:
<div data-attribute>
→ true<div data-attribute=''>
→ true<div data-attribute='true'>
→ true<div data-attribute='false'>
→ false<div data-attribute='1'>
→ 1 (also true)<div data-attribute='0'>
→ 0 (also false)<div data-attribute='value'>
→ value<div>
→ default
An element affected by an attribute searches for it amongst its own or ancestors' attributes.
<main data-attribute="1" >
<img /> <!-- → value=1 -->
<img data-attribute="2" /> <!-- → value=2 -->
</main>
Every frame is represented by an <article>
tag.
<article><img src="flower.jpg" /></article>
Which contains arbitrary HTML code, such as images or videos (by default, one per slide). Use control attributes:
(default 0
) How many seconds will a frame step last. By default, indefinitely (waiting for a user action).
<article data-duration="0.5">Short frame</article>
<article>You have to click to get further</article>
<article data-duration="0.5">Short frame</article>
Note a video frame is an exception: will hold till the video finishes and then change frame.
(default 0
) How many seconds will it take to change a frame.
(default spiral
) A viewport stands for a chessboard field. This is how the frame are positioned in the chessboard.
true=spiral
diagonal
Valid only for data-spread-frames=dialogal
. Overrides the default position. Attention, do not let the frames share the same position.
If present, images in the body will rapidly loop, creating a funny animation. (Currenly allowed only true
value for an infitite loop.)
<article data-loop>
<img src="pic1.jpg" />
<img src="pic2.jpg" />
</article>
Standard HTML ID serves for navigation.
<article>
<a href="#my_frame">go to a specific frame</a>
<a href="#my_section">go to the first frame in a specific section</a>
<a href="#2">go to the second frame (number may change if you add frames later)</a>
</article>
<section id=my_section>
<article>...</article>
<article id=my_frame>...</article>
</section>
You may use HTML comments just before the frame or as the first frame child. Markdown syntax is supported. These will be displayed in the auxiliary window while presenting.
Before the frame:
<!-- I should talk about cats. -->
<article><img src='cat.jpg' /></article>
Inside the frame:
<article>
<!-- I should talk about cats. -->
<img src='cat.jpg' />
</article>
Any HTML content is accepted.
A tag have following attributes:
(number in degrees) The content might easily be rotated. Use buttons in the menu to rotate live.
This element is not initially displayed but gradually appears as the user progresses through the presentation. If the value is not set, it will receive the next available unfilled number. If two elements share the same number, they will appear (or disappear) simultaneously. The numbers do not need to be assigned successively; you can skip values.
The property is not inherited, it concerns this particular element only. In gains .step-shown
or .step-hidden
class.
A basic example:
<article data-duration=1>
<p>Lorem ipsum</p>
<img data-step src="..."> <!-- displayed at step 1 -->
<p>dolor sit amet</p>
<img data-step src="..."> <!-- displayed at step 2 -->
<p data-step>consectetur adipiscing</p> <!-- displayed at step 3 -->
</article>
Steps work in a very intuitive way.
<article data-step-li>
<h1>Seen from the beginning</h1>
<ul>
<li>step 2</li>
<li>step 3</li>
<li data-step="1">step 1</li>
<li>step 6</li>
<li data-step="4">step 4</li>
<li>step 7</li>
</ul>
<p data-step>step 8</p>
<p data-step="100">step last</p> <!-- you can skip numbers -->
<p data-step="1">step 1 (too)</p>
<p data-step="5">step 5</p>
<p data-step>step 9</p>
<p data-step="5">step 5 (too)</p>
<p>seen from the beginning</p>
</article>
By default, the animation is fade in/out. It was made easy to change it. Example via pure CSS:
<style>
[data-step] {
/* all steps appear with a blue flash */
animation-name: blue-flash;
}
@keyframes blue-flash {
from {
background-color: blue;
}
to {
background-color: unset;
}
}
</style>
Any contained elements with [data-step]
will have this class set. Ignored when having [data-step-shown]
set. Example using Animate.css:
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" />
<article data-step-li>
<ul data-step-class="animate__animated animate__backInDown">
<li>I will fall from the top</li>
<li>Me too</li>
</ul>
</article>
Any contained elements with [data-step]
will not be hidden automatically. Instead of being shown, they receive the class given by the attribute. Supresses [data-step-class]
.
<style>
.my-class {font-weight: bold;}
</style>
<article data-step-li>
<ul>
<li>Will be shown.</li>
<li data-step-shown="my-class">Still visible. Becomes bold at step 2.</li>
</ul>
</article>
Every contained <li>
element is taken as having the data-step
attribute (see also: data-step
). They are not initially displayed but appears gradually as the user progresses through the presentation.
<article data-duration=1>
<ul data-step-li>
<li>Lorem</li> <!-- displayed at step 1 -->
<li>ipsum</li> <!-- displayed at step 2 -->
<li>dolor</li> <!-- displayed at step 3 -->
<li>sit</li> <!-- displayed at step 4 -->
</ul>
<ul>
<li>Always visible</li>
</ul>
</article>
How many seconds will a frame step last. By default, it takes data-duration
.
How many seconds will it take to change an image zoom step. By default, it takes data-transition-duration
.
We try to fetch Exif data for images.
data-device
: maker and modeldata-datetime
: picture time stamp (or fallback to file modification time)data-gps
: point on the map (HUD map will be automatically displayed in the corner)
However, this is a non-trivial task since the browser protects your photos privacy. This will work for images you drag and drop inside, images from the web (with the permitive CORS policy). Reading the Exif of your local images you just mention in the document will work only with the browser CORS disabled – do that only if you know what are you doing.
Zoomable on click/mouse wheel or a button from menu. You can zoom either an image or a video. Use arrows to crawl over the picture when zoomed. Even multiple arrows work at once. If you need the arrows to control the video playthrough, use Ctrl+arrows (works for both Firefox and Chrome).
Array of points an image should pass. The first is the initial image position. Works for the image itself or any contained image.
Point: [left = 0, top = 0, scale = 1, transition_duration = data-step-transition-duration | data-transition-duration, duration = data-step-duration | data-duration, data-rotate ]
In this example, the image starts at [100, 10, 2]
, then zooms out []
, then goes slowly (note the delay parameter) to [150,10,3,3]
. Next, while using the default transition_duration
(note the null
-> becomes 1.5
), we set duration
to 0.5 second for this step only [200,10,4,null,.5]
.
<article data-transition-duration=1.5>
<img data-step-points="[[100,10,2], [], [150,10,3,3], [200,10,4,null,1] , [250,10,5] , [300,10,6] , [350,10,7]]" src="..."/>
</article>
Position 0,0
is at the image centre. Its real dimension is taken into account so the value remains stable while changing the browser size (different displays). We recommend to use the property panel (Alt+P) to determine the coordinates.
Every image in the sections slowly zooms out from the center. (Images in header and footer are ignored.)
<section data-step-points="[[0,0,15,5], [0,0,1,5]]">
<article><img src="..."/></article>
<article><img src="..."/></article>
<article><img src="..."/></article>
</section>
When an image is much longer than the screen, we show it slowly first before resizing it to fit the screen. This will delay the <article>
's data-duration
. It starts when the image proportion width / height > data-panorama-threshold=2
.
When having thousands of images, your browser may choke. Use data-src
instead of src
as a preload.
<img data-src="flower.jpg" /> <!-- becomes <img src="flower.jpg"> when needed -->
<article>
<video controls autoplay muted loop>
<source src="my_video.mp4#t=8,10" type="video/mp4">
</video>
</article>
- Due to the
<source>
URL, only portion between 8 s – 10 s gets played. - The
<video>
tag benefits from standard attributes likeloop
,muted
,autoplay
andcontrols
(so that controls are visible). In Chromium based browsers, onlymuted
video respectsautoplay
so we recommend usingcontrols
too so that you may start the video with the Space. - When a new frame appears, first video gets focus. Whether
autoplay
is present, it starts playing. Keys like Space, Left, Right stop working for frame switching to avoid interfering with the video controls.
File modification time if available.
The speed of the video.
Tip: Can be adjusted by Numpad +/- while presenting (see menu – Esc).
<article> <!-- fast video -->
<video src="my_video.mp4#t=8,10" date-playback-rate="4"></video>
</article>
<article date-playback-rate="0.7"> <!-- slower video -->
<video src="my_video.mp4#t=8,10"></video>
</article>
(default 'autoplay controls'
) All <video>
tags inherits its value as attributes (autoplay controls muted loop
). Tip: toggle muted by Alt+M while presenting.
<article data-video="autoplay muted">
<video> <!-- becomes <video autoplay muted> -->
<source src="my_video.mp4#t=8,10" type="video/mp4">
</video>
</article>
<article>
<video src="my_video.mp4"> <!-- becomes <video autoplay controls> because that is the default --></video>
</article>
<article>
<video data-video='muted' src="my_video.mp4"> <!-- becomes <video muted> --></video>
</article>
<article>
<video muted src="my_video.mp4"> <!-- becomes <video autoplay controls muted> --></video>
</article>
Browsers allow you to specify the playback range by #t=[START],[STOP]
URL suffix. Should you wish to change the value dynamically, you may set it as video-cut.
Ex: <video src="myvideo.mp4#t=10></video>
will start playing at time 10 s.
Use the property panel (Alt+p) to help you with.
Array of events that happen during video playthrough. The format is: [startTime, rule, rule...]
.
The first item is the startTime
when other rules happen. Rules are following:
goto:[time]
– Where to jump. Ex:[10, "goto:50"]
means: At the time 10 s, jump to time 50 s.rate:[s]
– Ex:[10, "rate:.5"]
means: At the time 10 s, slow down to half.mute
– Toggles to muted sound.unmute
– Unmutes sound.pause
– Video stops.point:[data-step-point]
– Zoom to a point. This is defined by a standard data-step-point. Ex:[4, "goto:2.9", "point:[100,100,5]"]
means: At the time 4 s, jump back to time 2.9 s and zoom to a given point.
Use the property panel (Alt+p) to help you with creating video points.
You can place an arbitrary content inside an <article>
.
data-fit=auto
true|false
: Fit the text size to the screen width.auto
: Fit if there is no tag inside an<article>
These are map-related attributes which helps you to display the HUD/fullscreen map.
data-places
: Delimited by comma. Ex: "Prague, Brno"data-map-zoom
: Zoom as given by the Mapy.cz API (world 1, country 5, street 13)data-gps
: Single point, longitude and latitude, comma delimited.<!-- these are equivalent --> <img data-gps='50.0884647, 14.4707590' /> <img data-places='Prague' />
data-map-animate=true
: Change the center point directly (false
) or in a few steps (true
).data-map-geometry-show=false
: Route amongst the places. If a single place is given, we take the place from the last time.false
No route shown.route|true
Route is calculated amongst the places.line
Only line is marked amongst the places.
<!-- Full route is calculated and shown between Prague and Brno, then between Brno and Pardubice. --> <article-map data-duration="0" data-places="Prague" data-map-geometry-show="true"> <article-map data-places="Brno"></article-map> <article-map data-places="Pardubice"></article-map> </article-map>
data-map-geometry-criterion=''
: empty orfast
,short
,turist1
,turist2
,bike1
,bike2
,bike3
data-map-markers-show=true
: Show red marker of a point.data-map-geometry-clear=true
: Clear all route and drawings before displaying.data-map-markers-clear=true
: Clear all point markers. (Or keep them visible all.)
Normally any map command will incur a small HUD map in the corder to appear. Should you with to display the fullscreen map, use the <article-map>
tag.
You may nest <article-map>
tags easily which causes the map to change.
<article-map data-duration="0" data-places="Prague, Brno">
<article-map data-duration="0" data-places="Paris"></article-map>
<article-map data-duration="0.3" data-places="London"></article-map>
</article-map>
Note that no content is displayed within the <article-map>
in the moment.
You can easily convert a GPX file (exported from a map software) into an interactive route. Just launch this Python script onto a GPX file and copy the <article-map>
tags to the presentation HTML.
from pathlib import Path
import re
FILENAME = "export.gpx"
FRAME_COUNT = 10
tag_end = "</article-map>"
if matches:=re.findall('<trkpt lat="([^"]+)" lon="([^"]+)">', Path(FILENAME).read_text()):
# limit to frame count but always include the first and the last
step = round(len(matches)/(FRAME_COUNT-2))
limited = [matches[0]] + matches[::step][1:-1] + [matches[-1]]
# convert coordinates to frames
html = "\n".join([f'<article-map data-gps="{",".join((x[1], x[0]))}">{tag_end}' for x in limited])
# nest all under the first tag
html = re.sub(tag_end, "", html, 1) + tag_end
print(html)
These <article>
tags might be encapsuled into (nested) <section>
groups. A <section>
has the same attributes as an <article>
.
<section data-duration='0.5'>
<article>Short frame (inherits 0.5)</article>
<article data-duration='0'>You have to click to get further.</article>
<article>Short frame (inherits 0.5)</article>
</section>
As the ultimate default the <main>
tag may be used.
<main data-duration='0.5'>
<article>Short frame (inherits 0.5)</article>
</main>
Standard HTML ID attribute serves for navigation.
<article>
<a href="#my_frame">go to a specific frame</a>
<a href="#my_section">go to the first frame in a specific section</a>
<a href="#2">go to the second frame (number may change if you add frames later)</a>
</article>
<section id=my_section>
<article>...</article>
<article id=my_frame>...</article>
</section>
You may nest an <article>
beneath another one. Which causes the children to be hidden and shown on top of the parent when their time comes.
<article>
<img src="flower.jpg" />
<article>That is a flower!</article>
</article>
Tags <header>
and <footer>
used within a <template>
are automatically inserted into frames that does not yet contain such tags. (Put the <template>
outside <main>
.)
<template>
<footer>This is the default footer</footer>
</template>
<main>
<article>
Here we get an automatic footer
<!-- Inserted: <footer>This is the default footer</footer> -->
</article>
<article>
No footer will be appended here
<footer></footer>
</article>
</main>
The presentation being run in a simple HTML page, style customisation is really simple. Take a look at any running instance to the DevTools. For instance, to hide the frame counter, add a style tag to the <head>
.
<style>
#hud-counter {display:none}
</style>
Or adjust the green template (extra/green.css) that is being used in the tutorial.
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/CZ-NIC/slidershow@latest/extra/green.css" />
Media not shown: HEIF, MOV...
Some formats might not be supported in your browser. The is particullary unfortunate for the JXL format, which appears superiour. However is kept restrained by Google who pushes WebP instead.
Another issue arises from the patent mess surrounding x265: HEIF, HEVC. You might have luck with a less common browser that supports these formats.
If you have encouter difficulties with MOV, specifying the right codec might help.
GNU GPLv3.