Because the ~ 500 lines of code for slideshow.js
took much more time than I expected, I won't discuss the source in details (like I announced in the slide show of my last Blog). Instead I will try to show up the nuts and bolts I've come across when writing that script.
For a demo of that project you can always have a look at the slide show on my homepage. Within that show you also can find all sources.
This is not really difficult. I did it by copying the element into the slide:
1 | var displayInSlide = function(htmlElement) { |
Setting the element's outerHTML
to the innerHTML
of the slide frame does the work. Care must be taken when having an HTMLCollection
of slides retrieved by document.getElementsByClassName()
. This could be a "live" collection that detects the creation of a new (copied) element with the according class, and then you would have another slide in your show :-) Best is to copy the list of slides into an array before starting the show.
For normal text slides you will want them to be centered, and with a bigger font. Both is done by following CSS:
.slideframe {
display: flex;
justify-content: center;
align-items: center;
width: 98%;
height: 98%; /* enable vertical centering */
margin: auto; /* makes it centered, at least horizontally */
overflow: auto;
/* default font, overridable in slideshow.js */
font-size: 130%;
font-family: sans-serif;
}
It is the display: flex
that does the centering work here. Supported only by new browsers, not IE-10.
Mind that only slides smaller than the display area will be centered. When you want a centering effect also on bigger slides, or even long-text slides, you need to do something like the following:
var scaleText = function(slide) {
var downsizeTextVerticalPercent = 80;
var downsizeTextHorizontalPercent = 70;
// to keep vertical centering, set width/height only when content bigger than frame
// but this MUST be done, else long text would be cut on top
if (slide.scrollHeight >= controls.slideframe.clientHeight * downsizeTextVerticalPercent / 100)
slide.style.height = downsizeTextVerticalPercent+"%";
if (slide.scrollWidth >= controls.slideframe.clientWidth * downsizeTextHorizontalPercent / 100)
slide.style.width = downsizeTextHorizontalPercent+"%";
};
In HTML there is a tag called OBJECT
. This is a wildcard element that takes every kind of URL and tries its best to display that URL. When the URL points to a web page, it embeds a browser view into the current page, having its own scrollbar and event queue. You can not receive key presses or mouse clicks into that page by a JS event listener in parent page. It is like an IFRAME
element.
This works quite well for all kinds of resources, even for videos, although InternetExplorer (once again) will do things different, especially when rendering JS resources in an OBJECT
tag.
1 | var displayHyperLink = function(hyperLink) { |
You don't want your beautiful photos to appear in a complete wrong aspect ratio. The web advices us to use ....
img {
height: 100%;
width: auto;
}
.... for images that should have a dynamic size but want to keep their proportion. But what if the 100%
height would make parts of the image disappear because it's a landscape? Then you would like to define width to 100%
and height to auto
. I solved this using JS and CSS as follows:
.imageHeightTooBig {
height: 100%;
width: auto;
}
.imageWidthTooBig {
width: 100%;
height: auto;
}
var scaleImage = function(slide) {
// precondition is that image is loaded and displayed
var width = slide.naturalWidth || slide.scrollWidth;
var height = slide.naturalHeight || slide.scrollHeight;
var widthTooBig = (width >= controls.slideframe.clientWidth);
var heightTooBig = (height >= controls.slideframe.clientHeight);
if (widthTooBig || heightTooBig) {
var className = heightTooBig ? "imageHeightTooBig" : "imageWidthTooBig";
if (slide.className.indexOf(className) < 0)
slide.className += " "+className;
}
};
This code does nothing when neither height
nor width
of the image (slide) are too big. In any other case it finds out which of them is too big and applies the according CSS class to the element. When both are too big this would result in 100%
height and auto
width.
That way the image never should have a scrollbar, and its aspect ratio should be correct. When making the browser bigger and reloading the image you will have a bigger picture.
It is always better to have an image in original aspect-ratio and without scrollbars. To look deeper into it there should be some kind of zoom installed.
When having an slide show with a lot of high-resolution images you will wait a long time when loading that page. The browser would try to resolve all images as soon as the HTML has been rendered.
To avoid this you can remove the src
attribute from the IMG
tag and put the URL into the alt
attribute (= alternative text) instead. There must be some JS logic then that sets the src
attribute when the slide is displayed. This would look like the following:
1 | <body> |
There is no src
attributes on these IMG elements, thus the browser would not load anything. As soon as one of the elements is displayed, JS can set the src
attribute and thus start the image loading. When it is fully loaded, the image can be scaled and centered.
1 | if ( ! slide.src && slide.alt ) // lazy loading images have src in alt attribute |
I started to implement this with hash-tags (the part behind the '#' in browser address line). An URL of my slide show looks like this (for slide 25):
http://fritzthecat-blog.blogspot.co.at/2015/03/#25
Editing that number would take you to the desired slide, that was my intent.
I then detected that I needed to click the browser "Reload" button to actually load that slide, pressing the ENTER key was not enough (at least for Firefox). I introduced an "hashchange"
event listener and started to experiment, but quickly cancelled all efforts then, because this caused strange effects, slides were skipped and similar.
Unfortunately it is not possible to get back from the slide show to the previous location by using the browser "Back" button. Which is really ugly. I will try to realize this with HTML-5 history API.
AMD demands that you use no global variables. Every module X needed to do the work of a module Y should be defined as dependency of Y. It is then passed as parameter to the factory-function of Y by the AMD loader.
Other parameters that are not modules must be passed to the module-function that the factory-function returns. To have an imagination how that looks, here comes an example.
1 | "use strict"; |
This is the archetype you have to live with when applying AMD. The whole module definition is enclosed in a define()
call. The first parameter to it is an array of module identifiers, the second a factory function that produces the module when called by the AMD loader. The AMD loader resolves the dependencies and passes their results as parameters to the factory function. The result of that factory call is taken to be the module, be it an object or a function.
Working with my personal AMD loader I had to write an AMD caller for each HTML page that uses JS. This caller is for providing parameters to the module function. Here is an example.
1 | "use strict"; |
Here the AMD factory function is used to call the dependency module (slideshow
) with some page-specific configuration parameters.
A particular "pain in the brain" are circular references. Assume that module A needs B, but B needs also some services by A. Such should be resolved by cutting out from module A the part that module B needs, putting it to a new module C. Module B can than be built by passing C, and A can be built by passing B and C.
Implementing with AMD it not so easy. The same with refactoring. Being already difficult in JS, this is even more difficult in an AMD environment.
The deeper you go into JS, the more you find out that it is a writeonly language :-(
ɔ⃝ Fritz Ritzberger, 2015-03-24