HTML preprocessors can make writing HTML more powerful or convenient. For instance, Markdown is designed to be easier to write and read for text documents and you could write a loop in Pug.
In CodePen, whatever you write in the HTML editor is what goes within the <body>
tags in a basic HTML5 template. So you don't have access to higher-up elements like the <html>
tag. If you want to add classes there that can affect the whole document, this is the place to do it.
In CodePen, whatever you write in the HTML editor is what goes within the <body>
tags in a basic HTML5 template. If you need things in the <head>
of the document, put that code here.
The resource you are linking to is using the 'http' protocol, which may not work when the browser is using https.
CSS preprocessors help make authoring CSS easier. All of them offer things like variables and mixins to provide convenient abstractions.
It's a common practice to apply CSS to a page that styles elements such that they are consistent across all browsers. We offer two of the most popular choices: normalize.css and a reset. Or, choose Neither and nothing will be applied.
To get the best cross-browser support, it is a common practice to apply vendor prefixes to CSS properties and values that require them to work. For instance -webkit-
or -moz-
.
We offer two popular choices: Autoprefixer (which processes your CSS server-side) and -prefix-free (which applies prefixes via a script, client-side).
Any URLs added here will be added as <link>
s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.
You can apply CSS to your Pen from any stylesheet on the web. Just put a URL to it here and we'll apply it, in the order you have them, before the CSS in the Pen itself.
You can also link to another Pen here (use the .css
URL Extension) and we'll pull the CSS from that Pen and include it. If it's using a matching preprocessor, use the appropriate URL Extension and we'll combine the code before preprocessing, so you can use the linked Pen as a true dependency.
JavaScript preprocessors can help make authoring JavaScript easier and more convenient.
Babel includes JSX processing.
Any URL's added here will be added as <script>
s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.
You can apply a script from anywhere on the web to your Pen. Just put a URL to it here and we'll add it, in the order you have them, before the JavaScript in the Pen itself.
If the script you link to has the file extension of a preprocessor, we'll attempt to process it before applying.
You can also link to another Pen here, and we'll pull the JavaScript from that Pen and include it. If it's using a matching preprocessor, we'll combine the code before preprocessing, so you can use the linked Pen as a true dependency.
Search for and use JavaScript packages from npm here. By selecting a package, an import
statement will be added to the top of the JavaScript editor for this package.
Using packages here is powered by esm.sh, which makes packages from npm not only available on a CDN, but prepares them for native JavaScript ESM usage.
All packages are different, so refer to their docs for how they work.
If you're using React / ReactDOM, make sure to turn on Babel for the JSX processing.
If active, Pens will autosave every 30 seconds after being saved once.
If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.
If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.
Visit your global Editor Settings.
<section><a href="#selector" data-type="html" class="lightbox" data-group="inline">
インラインのHTML
</a>
<div style="display: none;">
<div id="selector" data-group="iframe">
hogehoge
<a href="#">foo</a>
</div>
</div>
</section>
<section>
<a href="https://www.youtube.com/embed/Yds5jgqmYpE?autoplay=1" data-type="iframe" class="lightbox" data-group="iframe-youtube" data-width="1120" data-height="630">
Youtube(直接リンクする場合)
</a>
</section>
<section>
<a href="#" data-type="youtube" data-id="RK1K2bCg4J8" class="lightbox" data-group="youtube">
Youtube(idを指定するタイプ)
</a>
</section>
<section>
<a href="https://via.placeholder.com/600.jpg" class="lightbox" data-group="gallery">
画像グループ1
</a> |
<a href="https://via.placeholder.com/500.jpg" class="lightbox" data-group="gallery">
画像グループ2
</a> |
<a href="https://via.placeholder.com/300.jpg" class="lightbox" data-group="gallery">
画像グループ3
</a>
</section>
<section>
</section>
:root {
--base-font-size: 18px;
--transition-duration: 0.3s;
--transition-timing-function: cubic-bezier(0.19, 1, 0.22, 1);
--zoom-icon-background: rgba(25, 41, 56, 0.94);
--zoom-icon-color: #fff;
--lightbox-background: rgba(25, 41, 56, 0.94);
--lightbox-z-index: 1337;
--caption-background: hsla(0, 0%, 100%, 0.94);
--caption-color: #192938;
--counter-background: transparent;
--counter-color: #fff;
--button-background: transparent;
--button-color: #fff;
--loader-color: #fff;
--slide-max-height: 85vh;
--slide-max-width: 85vw;
}
.tobii-zoom {
border: 0;
box-shadow: none;
display: inline-block;
position: relative;
text-decoration: none;
}
.tobii-zoom img {
display: block;
}
.tobii-zoom__icon {
align-items: center;
background-color: var(--zoom-icon-background);
top: 0.44444em;
color: var(--zoom-icon-color);
display: flex;
height: 1.77778em;
justify-content: center;
line-height: 1;
position: absolute;
right: 0.44444em;
width: 1.77778em;
}
.tobii-zoom__icon svg {
fill: none;
height: 1.33333em;
pointer-events: none;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 1.5;
stroke: currentColor;
width: 1.33333em;
}
.tobii-is-open {
overflow-y: hidden;
}
.tobii {
background-color: var(--lightbox-background);
bottom: 0;
box-sizing: border-box;
contain: strict;
font-size: var(--base-font-size);
left: 0;
line-height: 1.5;
overflow: hidden;
position: fixed;
right: 0;
top: 0;
z-index: var(--lightbox-z-index);
}
.tobii[aria-hidden="true"] {
display: none;
}
.tobii *,
.tobii :after,
.tobii :before {
box-sizing: inherit;
}
.tobii__slider {
bottom: 0;
left: 0;
position: absolute;
right: 0;
top: 0;
will-change: transform;
}
.tobii__slider[aria-hidden="true"] {
display: none;
}
@media screen and (prefers-reduced-motion: no-preference) {
.tobii__slider--animate:not(.tobii__slider--is-dragging) {
transition-duration: var(--transition-duration);
transition-property: transform;
transition-timing-function: var(--transition-timing-function);
}
}
.tobii__slider--is-draggable [data-type] {
cursor: grab;
}
.tobii__slider--is-dragging [data-type] {
cursor: grabbing;
}
.tobii__slide {
align-items: center;
display: flex;
height: 100%;
justify-content: center;
width: 100%;
}
.tobii__slide:not(.tobii__slide--is-active) {
visibility: hidden;
}
@media screen and (prefers-reduced-motion: no-preference) {
.tobii__slide:not(.tobii__slide--is-active) {
transition-duration: var(--transition-duration);
transition-property: visibility;
transition-timing-function: var(--transition-timing-function);
}
}
.tobii__slide [data-type] {
max-height: var(--slide-max-height);
max-width: var(--slide-max-width);
overflow: hidden;
overflow-y: auto;
-ms-scroll-chaining: none;
overscroll-behavior: contain;
}
.tobii__slide iframe,
.tobii__slide video {
display: block !important;
}
.tobii__slide figure {
margin: 0;
position: relative;
}
.tobii__slide figure > img {
display: block;
height: auto;
max-height: var(--slide-max-height);
max-width: var(--slide-max-width);
width: auto;
}
.tobii__slide figure > figcaption {
background-color: var(--caption-background);
bottom: 0;
color: var(--caption-color);
padding: 0.22222em 0.44444em;
position: absolute;
white-space: pre-wrap;
width: 100%;
}
.tobii__slide [data-type="html"] video {
cursor: auto;
max-height: var(--slide-max-height);
max-width: var(--slide-max-width);
}
.tobii__slide [data-type="iframe"] {
-webkit-overflow-scrolling: touch;
transform: translateZ(0);
}
.tobii__slide [data-type="iframe"] iframe {
height: var(--slide-max-height);
width: var(--slide-max-width);
}
.tobii__btn {
-webkit-appearance: none;
appearance: none;
background-color: var(--button-background);
border: 0.05556em solid transparent;
color: var(--button-color);
cursor: pointer;
font: inherit;
line-height: 1;
margin: 0;
opacity: 0.5;
padding: 0;
position: absolute;
touch-action: manipulation;
will-change: opacity;
z-index: 1;
}
@media screen and (prefers-reduced-motion: no-preference) {
.tobii__btn {
transition-duration: var(--transition-duration);
transition-property: opacity, transform;
transition-timing-function: var(--transition-timing-function);
will-change: opacity, transform;
}
}
.tobii__btn svg {
fill: none;
height: 3.33333em;
pointer-events: none;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 1;
stroke: currentColor;
width: 3.33333em;
}
.tobii__btn:active,
.tobii__btn:focus,
.tobii__btn:hover {
opacity: 1;
}
.tobii__btn--next,
.tobii__btn--previous {
top: 50%;
transform: translateY(-50%);
}
.tobii__btn--previous {
left: 0.88889em;
}
.tobii__btn--next {
right: 0.88889em;
}
.tobii__btn--close {
right: 0.88889em;
top: 0.88889em;
}
.tobii__btn:disabled,
.tobii__btn[aria-hidden="true"] {
display: none;
}
.tobii__counter {
background-color: var(--counter-background);
color: var(--counter-color);
font-size: 1.11111em;
left: 1em;
line-height: 1;
position: absolute;
top: 2.22222em;
z-index: 1;
}
.tobii__counter[aria-hidden="true"] {
display: none;
}
.tobii__loader {
display: inline-block;
height: 5.55556em;
left: 50%;
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
width: 5.55556em;
}
.tobii__loader:before {
animation: spin 1s infinite;
border-radius: 100%;
border: 0.22222em solid #949ba3;
border-top: 0.22222em solid var(--loader-color);
bottom: 0;
content: "";
left: 0;
position: absolute;
right: 0;
top: 0;
z-index: 1;
}
@keyframes spin {
to {
transform: rotate(1turn);
}
}
.tobii__slide .tobii-html {
background: #fff;
padding: 10px 20px;
max-width: 800px;
}
section {
margin: 1em;
}
/**
* Tobii
*
* @author midzer
* @version 2.0.7
* @url https://github.com/midzer/tobii
*
* MIT License
*/
export default function Tobii(userOptions) {
/**
* Global variables
*
*/
const FOCUSABLE_ELEMENTS = [
'a[href]:not([tabindex^="-"]):not([inert])',
'area[href]:not([tabindex^="-"]):not([inert])',
"input:not([disabled]):not([inert])",
"select:not([disabled]):not([inert])",
"textarea:not([disabled]):not([inert])",
"button:not([disabled]):not([inert])",
'iframe:not([tabindex^="-"]):not([inert])',
'audio:not([tabindex^="-"]):not([inert])',
'video:not([tabindex^="-"]):not([inert])',
'[contenteditable]:not([tabindex^="-"]):not([inert])',
'[tabindex]:not([tabindex^="-"]):not([inert])'
];
const WAITING_ELS = [];
const GROUP_ATTS = {
gallery: [],
slider: null,
sliderElements: [],
elementsLength: 0,
currentIndex: 0,
x: 0
};
const PLAYER = [];
let config = {};
let figcaptionId = 0;
let lightbox = null;
let prevButton = null;
let nextButton = null;
let closeButton = null;
let counter = null;
let drag = {};
let isDraggingX = false;
let isDraggingY = false;
let pointerDown = false;
let lastFocus = null;
let offset = null;
let offsetTmp = null;
let resizeTicking = false;
let isYouTubeDependencieLoaded = false;
let playerId = 0;
let groups = {};
let newGroup = null;
let activeGroup = null;
/**
* Merge default options with user options
*
* @param {Object} userOptions - Optional user options
* @returns {Object} - Custom options
*/
const mergeOptions = (userOptions) => {
// Default options
const OPTIONS = {
selector: ".lightbox",
captions: true,
captionsSelector: "img",
captionAttribute: "alt",
captionText: null,
nav: "auto",
navText: [
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path stroke="none" d="M0 0h24v24H0z"/><polyline points="15 6 9 12 15 18" /></svg>',
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path stroke="none" d="M0 0h24v24H0z"/><polyline points="9 6 15 12 9 18" /></svg>'
],
navLabel: ["Previous image", "Next image"],
close: true,
closeText:
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path stroke="none" d="M0 0h24v24H0z"/><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></svg>',
closeLabel: "Close lightbox",
loadingIndicatorLabel: "Image loading",
counter: true,
download: false, // TODO
downloadText: "", // TODO
downloadLabel: "Download image", // TODO
keyboard: true,
zoom: true,
zoomText:
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path stroke="none" d="M0 0h24v24H0z"/><polyline points="16 4 20 4 20 8" /><line x1="14" y1="10" x2="20" y2="4" /><polyline points="8 20 4 20 4 16" /><line x1="4" y1="20" x2="10" y2="14" /><polyline points="16 20 20 20 20 16" /><line x1="14" y1="14" x2="20" y2="20" /><polyline points="8 4 4 4 4 8" /><line x1="4" y1="4" x2="10" y2="10" /></svg>',
docClose: true,
swipeClose: true,
hideScrollbar: true,
draggable: true,
threshold: 100,
rtl: false, // TODO
loop: false, // TODO
autoplayVideo: false,
modal: false,
theme: "tobii--theme-default"
};
return {
...OPTIONS,
...userOptions
};
};
/**
* Types - you can add new type to support something new
*
*/
const SUPPORTED_ELEMENTS = {
image: {
checkSupport(el) {
return (
!el.hasAttribute("data-type") &&
el.href.match(/\.(png|jpe?g|tiff|tif|gif|bmp|webp|avif|svg|ico)(\?.*)?$/i)
);
},
init(el, container) {
const FIGURE = document.createElement("figure");
const FIGCAPTION = document.createElement("figcaption");
const IMAGE = document.createElement("img");
const THUMBNAIL = el.querySelector("img");
const LOADING_INDICATOR = document.createElement("div");
// Hide figure until the image is loaded
FIGURE.style.opacity = "0";
if (THUMBNAIL) {
IMAGE.alt = THUMBNAIL.alt || "";
}
IMAGE.setAttribute("src", "");
IMAGE.setAttribute("data-src", el.href);
if (el.hasAttribute("data-srcset")) {
IMAGE.setAttribute("srcset", el.getAttribute("data-srcset"));
}
// Add image to figure
FIGURE.appendChild(IMAGE);
// Create figcaption
if (config.captions) {
if (typeof config.captionText === "function") {
FIGCAPTION.textContent = config.captionText(el);
} else if (
config.captionsSelector === "self" &&
el.getAttribute(config.captionAttribute)
) {
FIGCAPTION.textContent = el.getAttribute(config.captionAttribute);
} else if (
config.captionsSelector === "img" &&
THUMBNAIL &&
THUMBNAIL.getAttribute(config.captionAttribute)
) {
FIGCAPTION.textContent = THUMBNAIL.getAttribute(config.captionAttribute);
}
if (FIGCAPTION.textContent) {
FIGCAPTION.id = `tobii-figcaption-${figcaptionId}`;
FIGURE.appendChild(FIGCAPTION);
IMAGE.setAttribute("aria-labelledby", FIGCAPTION.id);
++figcaptionId;
}
}
// Add figure to container
container.appendChild(FIGURE);
// Create loading indicator
LOADING_INDICATOR.className = "tobii__loader";
LOADING_INDICATOR.setAttribute("role", "progressbar");
LOADING_INDICATOR.setAttribute("aria-label", config.loadingIndicatorLabel);
// Add loading indicator to container
container.appendChild(LOADING_INDICATOR);
// Register type
container.setAttribute("data-type", "image");
container.classList.add("tobii-image");
},
onPreload(container) {
// Same as preload
SUPPORTED_ELEMENTS.image.onLoad(container);
},
onLoad(container) {
const IMAGE = container.querySelector("img");
if (!IMAGE.hasAttribute("data-src")) {
return;
}
const FIGURE = container.querySelector("figure");
const LOADING_INDICATOR = container.querySelector(".tobii__loader");
IMAGE.onload = () => {
container.removeChild(LOADING_INDICATOR);
FIGURE.style.opacity = "1";
};
IMAGE.setAttribute("src", IMAGE.getAttribute("data-src"));
IMAGE.removeAttribute("data-src");
},
onLeave(container) {
// Nothing
},
onCleanup(container) {
// Nothing
}
},
html: {
checkSupport(el) {
return checkType(el, "html");
},
init(el, container) {
const TARGET_SELECTOR = el.hasAttribute("data-target")
? el.getAttribute("data-target")
: el.getAttribute("href");
const TARGET = document.querySelector(TARGET_SELECTOR);
if (!TARGET) {
throw new Error(`Ups, I can't find the target ${TARGET_SELECTOR}.`);
}
// Add content to container
container.appendChild(TARGET);
// Register type
container.setAttribute("data-type", "html");
container.classList.add("tobii-html");
},
onPreload(container) {
// Nothing
},
onLoad(container) {
const VIDEO = container.querySelector("video");
if (VIDEO) {
if (VIDEO.hasAttribute("data-time") && VIDEO.readyState > 0) {
// Continue where video was stopped
VIDEO.currentTime = VIDEO.getAttribute("data-time");
}
if (config.autoplayVideo) {
// Start playback (and loading if necessary)
VIDEO.play();
}
}
},
onLeave(container) {
const VIDEO = container.querySelector("video");
if (VIDEO) {
if (!VIDEO.paused) {
// Stop if video is playing
VIDEO.pause();
}
// Backup currentTime (needed for revisit)
if (VIDEO.readyState > 0) {
VIDEO.setAttribute("data-time", VIDEO.currentTime);
}
}
},
onCleanup(container) {
const VIDEO = container.querySelector("video");
if (VIDEO) {
if (
VIDEO.readyState > 0 &&
VIDEO.readyState < 3 &&
VIDEO.duration !== VIDEO.currentTime
) {
// Some data has been loaded but not the whole package.
// In order to save bandwidth, stop downloading as soon as possible.
const VIDEO_CLONE = VIDEO.cloneNode(true);
removeSources(VIDEO);
VIDEO.load();
VIDEO.parentNode.removeChild(VIDEO);
container.appendChild(VIDEO_CLONE);
}
}
}
},
iframe: {
checkSupport(el) {
return checkType(el, "iframe");
},
init(el, container) {
const IFRAME = document.createElement("iframe");
const HREF = el.hasAttribute("data-target")
? el.getAttribute("data-target")
: el.getAttribute("href");
IFRAME.setAttribute("frameborder", "0");
IFRAME.setAttribute("src", "");
IFRAME.setAttribute("allowfullscreen", "");
IFRAME.setAttribute("data-src", HREF);
// Hide until loaded
IFRAME.style.opacity = "0";
// set allow parameters
if (HREF.indexOf("youtube.com") > -1) {
IFRAME.setAttribute(
"allow",
"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
);
} else if (HREF.indexOf("vimeo.com") > -1) {
IFRAME.setAttribute("allow", "autoplay; picture-in-picture");
} else if (el.hasAttribute("data-allow")) {
IFRAME.setAttribute("allow", el.getAttribute("data-allow"));
}
if (el.getAttribute("data-width")) {
IFRAME.style.maxWidth = `${el.getAttribute("data-width")}px`;
}
if (el.getAttribute("data-height")) {
IFRAME.style.maxHeight = `${el.getAttribute("data-height")}px`;
}
// Add iframe to container
container.appendChild(IFRAME);
// Register type
container.setAttribute("data-type", "iframe");
container.classList.add("tobii-iframe");
},
onPreload(container) {
// Nothing
},
onLoad(container) {
const IFRAME = container.querySelector("iframe");
IFRAME.setAttribute("src", IFRAME.getAttribute("data-src"));
IFRAME.onload = () => {
IFRAME.style.opacity = "1";
};
},
onLeave(container) {
// Nothing
},
onCleanup(container) {
const IFRAME = container.querySelector("iframe");
IFRAME.setAttribute("src", "");
IFRAME.style.opacity = "0";
}
},
youtube: {
checkSupport(el) {
return checkType(el, "youtube");
},
init(el, container) {
const IFRAME_PLACEHOLDER = document.createElement("div");
// Add iframePlaceholder to container
container.appendChild(IFRAME_PLACEHOLDER);
PLAYER[playerId] = new window.YT.Player(IFRAME_PLACEHOLDER, {
host: "https://www.youtube-nocookie.com",
height: el.getAttribute("data-height") || "360",
width: el.getAttribute("data-width") || "640",
videoId: el.getAttribute("data-id"),
playerVars: {
controls: el.getAttribute("data-controls") || 1,
rel: 0,
playsinline: 1
}
});
// Set player ID
container.setAttribute("data-player", playerId);
// Register type
container.setAttribute("data-type", "youtube");
container.classList.add("tobii-youtube");
playerId++;
},
onPreload(container) {
// Nothing
},
onLoad(container) {
if (config.autoplayVideo) {
PLAYER[container.getAttribute("data-player")].playVideo();
}
},
onLeave(container) {
if (PLAYER[container.getAttribute("data-player")].getPlayerState() === 1) {
PLAYER[container.getAttribute("data-player")].pauseVideo();
}
},
onCleanup(container) {
if (PLAYER[container.getAttribute("data-player")].getPlayerState() === 1) {
PLAYER[container.getAttribute("data-player")].pauseVideo();
}
}
}
};
/**
* Init
*
*/
const init = (userOptions) => {
// Merge user options into defaults
config = mergeOptions(userOptions);
// Check if the lightbox already exists
if (!lightbox) {
createLightbox();
}
// Get a list of all elements within the document
const LIGHTBOX_TRIGGER_ELS = document.querySelectorAll(config.selector);
if (!LIGHTBOX_TRIGGER_ELS) {
throw new Error(
`Ups, I can't find the selector ${config.selector} on this website.`
);
}
// Execute a few things once per element
LIGHTBOX_TRIGGER_ELS.forEach((lightboxTriggerEl) => {
checkDependencies(lightboxTriggerEl);
});
};
/**
* Check dependencies
*
* @param {HTMLElement} el - Element to add
*/
const checkDependencies = (el) => {
// Check if there is a YouTube video and if the YouTube iframe-API is ready
if (
document.querySelector('[data-type="youtube"]') !== null &&
!isYouTubeDependencieLoaded
) {
if (document.getElementById("iframe_api") === null) {
const TAG = document.createElement("script");
const FIRST_SCRIPT_TAG = document.getElementsByTagName("script")[0];
TAG.id = "iframe_api";
TAG.src = "https://www.youtube.com/iframe_api";
FIRST_SCRIPT_TAG.parentNode.insertBefore(TAG, FIRST_SCRIPT_TAG);
}
if (WAITING_ELS.indexOf(el) === -1) {
WAITING_ELS.push(el);
}
window.onYouTubePlayerAPIReady = () => {
WAITING_ELS.forEach((waitingEl) => {
add(waitingEl);
});
isYouTubeDependencieLoaded = true;
};
} else {
add(el);
}
};
/**
* Get group name from element
*
* @param {HTMLElement} el
* @return {string}
*/
const getGroupName = (el) => {
return el.hasAttribute("data-group")
? el.getAttribute("data-group")
: "default";
};
/**
* Copy an object. (The secure way)
*
* @param {object} object
* @return {object}
*/
const copyObject = (object) => {
return JSON.parse(JSON.stringify(object));
};
/**
* Add element
*
* @param {HTMLElement} el - Element to add
*/
const add = (el) => {
newGroup = getGroupName(el);
if (!Object.prototype.hasOwnProperty.call(groups, newGroup)) {
groups[newGroup] = copyObject(GROUP_ATTS);
createSlider();
}
// Check if element already exists
if (groups[newGroup].gallery.indexOf(el) === -1) {
groups[newGroup].gallery.push(el);
groups[newGroup].elementsLength++;
// Set zoom icon if necessary
if (config.zoom && el.querySelector("img")) {
const TOBII_ZOOM = document.createElement("div");
TOBII_ZOOM.className = "tobii-zoom__icon";
TOBII_ZOOM.innerHTML = config.zoomText;
el.classList.add("tobii-zoom");
el.appendChild(TOBII_ZOOM);
}
// Bind click event handler
el.addEventListener("click", triggerTobii);
createSlide(el);
if (isOpen() && newGroup === activeGroup) {
updateConfig();
updateLightbox();
}
} else {
throw new Error("Ups, element already added.");
}
};
/**
* Remove element
*
* @param {HTMLElement} el - Element to remove
*/
const remove = (el) => {
const GROUP_NAME = getGroupName(el);
// Check if element exists
if (groups[GROUP_NAME].gallery.indexOf(el) === -1) {
throw new Error(`Ups, I can't find a slide for the element ${el}.`);
} else {
const SLIDE_INDEX = groups[GROUP_NAME].gallery.indexOf(el);
const SLIDE_EL = groups[GROUP_NAME].sliderElements[SLIDE_INDEX];
// If the element to be removed is the currently visible slide
if (
isOpen() &&
GROUP_NAME === activeGroup &&
SLIDE_INDEX === groups[GROUP_NAME].currentIndex
) {
if (groups[GROUP_NAME].elementsLength === 1) {
close();
throw new Error("Ups, I've closed. There are no slides more to show.");
} else {
// TODO If there is only one slide left, deactivate horizontal dragging/ swiping
// TODO Recalculate counter
// TODO Set new absolute position per slide
// If the first slide is displayed
if (groups[GROUP_NAME].currentIndex === 0) {
next();
} else {
previous();
}
}
}
// TODO Remove element
// groups[GROUP_NAME].gallery.splice(groups[GROUP_NAME].gallery.indexOf(el)) don't work
groups[GROUP_NAME].elementsLength--;
// Remove zoom icon if necessary
if (config.zoom && el.querySelector(".tobii-zoom__icon")) {
const ZOOM_ICON = el.querySelector(".tobii-zoom__icon");
ZOOM_ICON.parentNode.classList.remove("tobii-zoom");
ZOOM_ICON.parentNode.removeChild(ZOOM_ICON);
}
// Unbind click event handler
el.removeEventListener("click", triggerTobii);
// Remove slide
SLIDE_EL.parentNode.removeChild(SLIDE_EL);
}
};
/**
* Create the lightbox
*
*/
const createLightbox = () => {
// Create the lightbox container
lightbox = document.createElement("div");
lightbox.setAttribute("role", "dialog");
lightbox.setAttribute("aria-hidden", "true");
lightbox.classList.add("tobii");
// Adc theme class
lightbox.classList.add(config.theme);
// Create the previous button
prevButton = document.createElement("button");
prevButton.className = "tobii__btn tobii__btn--previous";
prevButton.setAttribute("type", "button");
prevButton.setAttribute("aria-label", config.navLabel[0]);
prevButton.innerHTML = config.navText[0];
lightbox.appendChild(prevButton);
// Create the next button
nextButton = document.createElement("button");
nextButton.className = "tobii__btn tobii__btn--next";
nextButton.setAttribute("type", "button");
nextButton.setAttribute("aria-label", config.navLabel[1]);
nextButton.innerHTML = config.navText[1];
lightbox.appendChild(nextButton);
// Create the close button
closeButton = document.createElement("button");
closeButton.className = "tobii__btn tobii__btn--close";
closeButton.setAttribute("type", "button");
closeButton.setAttribute("aria-label", config.closeLabel);
closeButton.innerHTML = config.closeText;
lightbox.appendChild(closeButton);
// Create the counter
counter = document.createElement("div");
counter.className = "tobii__counter";
lightbox.appendChild(counter);
document.body.appendChild(lightbox);
};
/**
* Create a slider
*/
const createSlider = () => {
groups[newGroup].slider = document.createElement("div");
groups[newGroup].slider.className = "tobii__slider";
// Hide slider
groups[newGroup].slider.setAttribute("aria-hidden", "true");
lightbox.appendChild(groups[newGroup].slider);
};
/**
* Create a slide
*
*/
const createSlide = (el) => {
// Detect type
for (const index in SUPPORTED_ELEMENTS) {
if (Object.prototype.hasOwnProperty.call(SUPPORTED_ELEMENTS, index)) {
if (SUPPORTED_ELEMENTS[index].checkSupport(el)) {
// Create slide elements
const SLIDER_ELEMENT = document.createElement("div");
const SLIDER_ELEMENT_CONTENT = document.createElement("div");
SLIDER_ELEMENT.className = "tobii__slide";
SLIDER_ELEMENT.style.position = "absolute";
SLIDER_ELEMENT.style.left = `${groups[newGroup].x * 100}%`;
// Hide slide
SLIDER_ELEMENT.setAttribute("aria-hidden", "true");
// Create type elements
SUPPORTED_ELEMENTS[index].init(el, SLIDER_ELEMENT_CONTENT);
// Add slide content container to slider element
SLIDER_ELEMENT.appendChild(SLIDER_ELEMENT_CONTENT);
// Add slider element to slider
groups[newGroup].slider.appendChild(SLIDER_ELEMENT);
groups[newGroup].sliderElements.push(SLIDER_ELEMENT);
++groups[newGroup].x;
break;
}
}
}
};
/**
* Open Tobii
*
* @param {number} index - Index to load
*/
const open = (index) => {
activeGroup = activeGroup !== null ? activeGroup : newGroup;
if (isOpen()) {
throw new Error("Ups, I'm aleady open.");
}
if (!isOpen()) {
if (!index) {
index = 0;
}
if (index === -1 || index >= groups[activeGroup].elementsLength) {
throw new Error(`Ups, I can't find slide ${index}.`);
}
}
if (config.hideScrollbar) {
document.documentElement.classList.add("tobii-is-open");
document.body.classList.add("tobii-is-open");
}
updateConfig();
// Hide close if necessary
if (!config.close) {
closeButton.disabled = false;
closeButton.setAttribute("aria-hidden", "true");
}
// Save user’s focus
lastFocus = document.activeElement;
// Use `history.pushState()` to make sure the "Back" button behavior
// that aligns with the user's expectations
const stateObj = {
tobii: "close"
};
const url = window.location.href;
window.history.pushState(stateObj, "Image", url);
// Set current index
groups[activeGroup].currentIndex = index;
clearDrag();
bindEvents();
// Load slide
load(groups[activeGroup].currentIndex);
// Show slider
groups[activeGroup].slider.setAttribute("aria-hidden", "false");
// Show lightbox
lightbox.setAttribute("aria-hidden", "false");
updateLightbox();
// Preload previous and next slide
preload(groups[activeGroup].currentIndex + 1);
preload(groups[activeGroup].currentIndex - 1);
// Hack to prevent animation during opening
setTimeout(() => {
groups[activeGroup].slider.classList.add("tobii__slider--animate");
}, 1000);
// Create and dispatch a new event
const openEvent = new window.CustomEvent("open", {
detail: {
group: activeGroup
}
});
lightbox.dispatchEvent(openEvent);
};
/**
* Close Tobii
*
*/
const close = () => {
if (!isOpen()) {
throw new Error("Ups, I'm already closed.");
}
if (config.hideScrollbar) {
document.documentElement.classList.remove("tobii-is-open");
document.body.classList.remove("tobii-is-open");
}
unbindEvents();
// Remove entry in browser history
if (window.history.state !== null) {
if (window.history.state.tobii === "close") {
window.history.back();
}
}
// Reenable the user’s focus
lastFocus.focus();
// Don't forget to cleanup our current element
leave(groups[activeGroup].currentIndex);
cleanup(groups[activeGroup].currentIndex);
// Hide lightbox
lightbox.setAttribute("aria-hidden", "true");
// Hide slider
groups[activeGroup].slider.setAttribute("aria-hidden", "true");
// Reset current index
groups[activeGroup].currentIndex = 0;
// Remove the hack to prevent animation during opening
groups[activeGroup].slider.classList.remove("tobii__slider--animate");
// Create and dispatch a new event
const closeEvent = new window.CustomEvent("close", {
detail: {
group: activeGroup
}
});
lightbox.dispatchEvent(closeEvent);
};
/**
* Preload slide
*
* @param {number} index - Index to preload
*/
const preload = (index) => {
if (groups[activeGroup].sliderElements[index] === undefined) {
return;
}
const CONTAINER = groups[activeGroup].sliderElements[index].querySelector(
"[data-type]"
);
const TYPE = CONTAINER.getAttribute("data-type");
SUPPORTED_ELEMENTS[TYPE].onPreload(CONTAINER);
};
/**
* Load slide
* Will be called when opening the lightbox or moving index
*
* @param {number} index - Index to load
*/
const load = (index) => {
if (groups[activeGroup].sliderElements[index] === undefined) {
return;
}
const CONTAINER = groups[activeGroup].sliderElements[index].querySelector(
"[data-type]"
);
const TYPE = CONTAINER.getAttribute("data-type");
// Add active slide class
groups[activeGroup].sliderElements[index].classList.add(
"tobii__slide--is-active"
);
groups[activeGroup].sliderElements[index].setAttribute(
"aria-hidden",
"false"
);
SUPPORTED_ELEMENTS[TYPE].onLoad(CONTAINER);
};
/**
* Select a slide
*
* @param {number} index - Index to select
*/
const select = (index) => {
const currIndex = groups[activeGroup].currentIndex;
if (!isOpen()) {
throw new Error("Ups, I'm closed.");
}
if (isOpen()) {
if (!index && index !== 0) {
throw new Error("Ups, no slide specified.");
}
if (index === groups[activeGroup].currentIndex) {
throw new Error(`Ups, slide ${index} is already selected.`);
}
if (index === -1 || index >= groups[activeGroup].elementsLength) {
throw new Error(`Ups, I can't find slide ${index}.`);
}
}
// Set current index
groups[activeGroup].currentIndex = index;
leave(currIndex);
load(index);
if (index < currIndex) {
updateLightbox("left");
cleanup(currIndex);
preload(index - 1);
}
if (index > currIndex) {
updateLightbox("right");
cleanup(currIndex);
preload(index + 1);
}
};
/**
* Select the previous slide
*
*/
const previous = () => {
if (!isOpen()) {
throw new Error("Ups, I'm closed.");
}
if (groups[activeGroup].currentIndex > 0) {
leave(groups[activeGroup].currentIndex);
load(--groups[activeGroup].currentIndex);
updateLightbox("left");
cleanup(groups[activeGroup].currentIndex + 1);
preload(groups[activeGroup].currentIndex - 1);
}
// Create and dispatch a new event
const previousEvent = new window.CustomEvent("previous", {
detail: {
group: activeGroup
}
});
lightbox.dispatchEvent(previousEvent);
};
/**
* Select the next slide
*
*/
const next = () => {
if (!isOpen()) {
throw new Error("Ups, I'm closed.");
}
if (
groups[activeGroup].currentIndex <
groups[activeGroup].elementsLength - 1
) {
leave(groups[activeGroup].currentIndex);
load(++groups[activeGroup].currentIndex);
updateLightbox("right");
cleanup(groups[activeGroup].currentIndex - 1);
preload(groups[activeGroup].currentIndex + 1);
}
// Create and dispatch a new event
const nextEvent = new window.CustomEvent("next", {
detail: {
group: activeGroup
}
});
lightbox.dispatchEvent(nextEvent);
};
/**
* Select a group
*
* @param {string} name - Name of the group to select
*/
const selectGroup = (name) => {
if (isOpen()) {
throw new Error("Ups, I'm open.");
}
if (!name) {
throw new Error("Ups, no group specified.");
}
if (name && !Object.prototype.hasOwnProperty.call(groups, name)) {
throw new Error(`Ups, I don't have a group called "${name}".`);
}
activeGroup = name;
};
/**
* Leave slide
* Will be called before moving index
*
* @param {number} index - Index to leave
*/
const leave = (index) => {
if (groups[activeGroup].sliderElements[index] === undefined) {
return;
}
const CONTAINER = groups[activeGroup].sliderElements[index].querySelector(
"[data-type]"
);
const TYPE = CONTAINER.getAttribute("data-type");
// Remove active slide class
groups[activeGroup].sliderElements[index].classList.remove(
"tobii__slide--is-active"
);
groups[activeGroup].sliderElements[index].setAttribute("aria-hidden", "true");
SUPPORTED_ELEMENTS[TYPE].onLeave(CONTAINER);
};
/**
* Cleanup slide
* Will be called after moving index
*
* @param {number} index - Index to cleanup
*/
const cleanup = (index) => {
if (groups[activeGroup].sliderElements[index] === undefined) {
return;
}
const CONTAINER = groups[activeGroup].sliderElements[index].querySelector(
"[data-type]"
);
const TYPE = CONTAINER.getAttribute("data-type");
SUPPORTED_ELEMENTS[TYPE].onCleanup(CONTAINER);
};
/**
* Update offset
*
*/
const updateOffset = () => {
activeGroup = activeGroup !== null ? activeGroup : newGroup;
offset = -groups[activeGroup].currentIndex * lightbox.offsetWidth;
groups[activeGroup].slider.style.transform = `translate3d(${offset}px, 0, 0)`;
offsetTmp = offset;
};
/**
* Update counter
*
*/
const updateCounter = () => {
counter.textContent = `${groups[activeGroup].currentIndex + 1}/${
groups[activeGroup].elementsLength
}`;
};
/**
* Update focus
*
* @param {string} dir - Current slide direction
*/
const updateFocus = (dir) => {
if (
(config.nav === true || config.nav === "auto") &&
!isTouchDevice() &&
groups[activeGroup].elementsLength > 1
) {
prevButton.setAttribute("aria-hidden", "true");
prevButton.disabled = true;
nextButton.setAttribute("aria-hidden", "true");
nextButton.disabled = true;
// If there is only one slide
if (groups[activeGroup].elementsLength === 1) {
if (config.close) {
closeButton.focus();
}
} else {
// If the first slide is displayed
if (groups[activeGroup].currentIndex === 0) {
nextButton.setAttribute("aria-hidden", "false");
nextButton.disabled = false;
nextButton.focus();
// If the last slide is displayed
} else if (
groups[activeGroup].currentIndex ===
groups[activeGroup].elementsLength - 1
) {
prevButton.setAttribute("aria-hidden", "false");
prevButton.disabled = false;
prevButton.focus();
} else {
prevButton.setAttribute("aria-hidden", "false");
prevButton.disabled = false;
nextButton.setAttribute("aria-hidden", "false");
nextButton.disabled = false;
if (dir === "left") {
prevButton.focus();
} else {
nextButton.focus();
}
}
}
} else if (config.close) {
closeButton.focus();
}
};
/**
* Clear drag after touchend and mousup event
*
*/
const clearDrag = () => {
drag = {
startX: 0,
endX: 0,
startY: 0,
endY: 0
};
};
/**
* Recalculate drag / swipe event
*
*/
const updateAfterDrag = () => {
const MOVEMENT_X = drag.endX - drag.startX;
const MOVEMENT_Y = drag.endY - drag.startY;
const MOVEMENT_X_DISTANCE = Math.abs(MOVEMENT_X);
const MOVEMENT_Y_DISTANCE = Math.abs(MOVEMENT_Y);
if (
MOVEMENT_X > 0 &&
MOVEMENT_X_DISTANCE > config.threshold &&
groups[activeGroup].currentIndex > 0
) {
previous();
} else if (
MOVEMENT_X < 0 &&
MOVEMENT_X_DISTANCE > config.threshold &&
groups[activeGroup].currentIndex !== groups[activeGroup].elementsLength - 1
) {
next();
} else if (
MOVEMENT_Y < 0 &&
MOVEMENT_Y_DISTANCE > config.threshold &&
config.swipeClose
) {
close();
} else {
updateOffset();
}
};
/**
* Resize event using requestAnimationFrame
*
*/
const resizeHandler = () => {
if (!resizeTicking) {
resizeTicking = true;
window.requestAnimationFrame(() => {
updateOffset();
resizeTicking = false;
});
}
};
/**
* Click event handler to trigger Tobii
*
*/
const triggerTobii = (event) => {
event.preventDefault();
activeGroup = getGroupName(event.currentTarget);
open(groups[activeGroup].gallery.indexOf(event.currentTarget));
};
/**
* Click event handler
*
*/
const clickHandler = (event) => {
if (event.target === prevButton) {
previous();
} else if (event.target === nextButton) {
next();
} else if (
event.target === closeButton ||
(isDraggingX === false &&
isDraggingY === false &&
event.target.classList.contains("tobii__slide") &&
config.docClose)
) {
close();
}
event.stopPropagation();
};
/**
* Get the focusable children of the given element
*
* @return {Array<Element>}
*/
const getFocusableChildren = () => {
return Array.prototype.slice
.call(
lightbox.querySelectorAll(
`.tobii__btn:not([disabled]), .tobii__slide--is-active + ${FOCUSABLE_ELEMENTS.join(
", .tobii__slide--is-active "
)}`
)
)
.filter((child) => {
return !!(
child.offsetWidth ||
child.offsetHeight ||
child.getClientRects().length
);
});
};
/**
* Keydown event handler
*
* @TODO: Remove the deprecated event.keyCode when Edge support event.code and we drop f*cking IE
* @see https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode
*/
const keydownHandler = (event) => {
const FOCUSABLE_CHILDREN = getFocusableChildren();
const FOCUSED_ITEM_INDEX = FOCUSABLE_CHILDREN.indexOf(document.activeElement);
if (event.keyCode === 9 || event.code === "Tab") {
// If the SHIFT key is being pressed while tabbing (moving backwards) and
// the currently focused item is the first one, move the focus to the last
// focusable item from the slide
if (event.shiftKey && FOCUSED_ITEM_INDEX === 0) {
FOCUSABLE_CHILDREN[FOCUSABLE_CHILDREN.length - 1].focus();
event.preventDefault();
// If the SHIFT key is not being pressed (moving forwards) and the currently
// focused item is the last one, move the focus to the first focusable item
// from the slide
} else if (
!event.shiftKey &&
FOCUSED_ITEM_INDEX === FOCUSABLE_CHILDREN.length - 1
) {
FOCUSABLE_CHILDREN[0].focus();
event.preventDefault();
}
} else if (event.keyCode === 27 || event.code === "Escape") {
// `ESC` Key: Close Tobii
event.preventDefault();
close();
} else if (event.keyCode === 37 || event.code === "ArrowLeft") {
// `PREV` Key: Show the previous slide
event.preventDefault();
previous();
} else if (event.keyCode === 39 || event.code === "ArrowRight") {
// `NEXT` Key: Show the next slide
event.preventDefault();
next();
}
};
/**
* Touchstart event handler
*
*/
const touchstartHandler = (event) => {
// Prevent dragging / swiping on textareas inputs and selects
if (isIgnoreElement(event.target)) {
return;
}
event.stopPropagation();
isDraggingX = false;
isDraggingY = false;
pointerDown = true;
drag.startX = event.touches[0].pageX;
drag.startY = event.touches[0].pageY;
groups[activeGroup].slider.classList.add("tobii__slider--is-dragging");
};
/**
* Touchmove event handler
*
*/
const touchmoveHandler = (event) => {
event.stopPropagation();
if (pointerDown) {
event.preventDefault();
drag.endX = event.touches[0].pageX;
drag.endY = event.touches[0].pageY;
doSwipe();
}
};
/**
* Touchend event handler
*
*/
const touchendHandler = (event) => {
event.stopPropagation();
pointerDown = false;
groups[activeGroup].slider.classList.remove("tobii__slider--is-dragging");
if (drag.endX) {
updateAfterDrag();
}
clearDrag();
};
/**
* Mousedown event handler
*
*/
const mousedownHandler = (event) => {
// Prevent dragging / swiping on textareas inputs and selects
if (isIgnoreElement(event.target)) {
return;
}
event.preventDefault();
event.stopPropagation();
isDraggingX = false;
isDraggingY = false;
pointerDown = true;
drag.startX = event.pageX;
drag.startY = event.pageY;
groups[activeGroup].slider.classList.add("tobii__slider--is-dragging");
};
/**
* Mousemove event handler
*
*/
const mousemoveHandler = (event) => {
event.preventDefault();
if (pointerDown) {
drag.endX = event.pageX;
drag.endY = event.pageY;
doSwipe();
}
};
/**
* Mouseup event handler
*
*/
const mouseupHandler = (event) => {
event.stopPropagation();
pointerDown = false;
groups[activeGroup].slider.classList.remove("tobii__slider--is-dragging");
if (drag.endX) {
updateAfterDrag();
}
clearDrag();
};
/**
* Contextmenu event handler
* This is a fix for chromium based browser on mac.
* The 'contextmenu' terminates a mouse event sequence.
* https://bugs.chromium.org/p/chromium/issues/detail?id=506801
*
*/
const contextmenuHandler = () => {
pointerDown = false;
};
/**
* Decide whether to do horizontal of vertical swipe
*
*/
const doSwipe = () => {
if (
Math.abs(drag.startX - drag.endX) > 0 &&
!isDraggingY &&
groups[activeGroup].elementsLength > 1
) {
// Horizontal swipe
groups[activeGroup].slider.style.transform = `translate3d(${
offsetTmp - Math.round(drag.startX - drag.endX)
}px, 0, 0)`;
isDraggingX = true;
isDraggingY = false;
} else if (
Math.abs(drag.startY - drag.endY) > 0 &&
!isDraggingX &&
config.swipeClose
) {
// Vertical swipe
groups[
activeGroup
].slider.style.transform = `translate3d(${offsetTmp}px, -${Math.round(
drag.startY - drag.endY
)}px, 0)`;
isDraggingX = false;
isDraggingY = true;
}
};
/**
* Bind events
*
*/
const bindEvents = () => {
if (config.keyboard) {
window.addEventListener("keydown", keydownHandler);
}
// Resize event
window.addEventListener("resize", resizeHandler);
// Popstate event
window.addEventListener("popstate", close);
// Click event
lightbox.addEventListener("click", clickHandler);
if (config.draggable) {
if (isTouchDevice()) {
// Touch events
lightbox.addEventListener("touchstart", touchstartHandler);
lightbox.addEventListener("touchmove", touchmoveHandler);
lightbox.addEventListener("touchend", touchendHandler);
}
// Mouse events
lightbox.addEventListener("mousedown", mousedownHandler);
lightbox.addEventListener("mouseup", mouseupHandler);
lightbox.addEventListener("mousemove", mousemoveHandler);
lightbox.addEventListener("contextmenu", contextmenuHandler);
}
};
/**
* Unbind events
*
*/
const unbindEvents = () => {
if (config.keyboard) {
window.removeEventListener("keydown", keydownHandler);
}
// Resize event
window.removeEventListener("resize", resizeHandler);
// Popstate event
window.removeEventListener("popstate", close);
// Click event
lightbox.removeEventListener("click", clickHandler);
if (config.draggable) {
if (isTouchDevice()) {
// Touch events
lightbox.removeEventListener("touchstart", touchstartHandler);
lightbox.removeEventListener("touchmove", touchmoveHandler);
lightbox.removeEventListener("touchend", touchendHandler);
}
// Mouse events
lightbox.removeEventListener("mousedown", mousedownHandler);
lightbox.removeEventListener("mouseup", mouseupHandler);
lightbox.removeEventListener("mousemove", mousemoveHandler);
lightbox.removeEventListener("contextmenu", contextmenuHandler);
}
};
/**
* Checks whether element has requested data-type value
*
*/
const checkType = (el, type) => {
return el.getAttribute("data-type") === type;
};
/**
* Remove all `src` attributes
*
* @param {HTMLElement} el - Element to remove all `src` attributes
*/
const removeSources = (el) => {
const SOURCES = el.querySelectorAll("src");
if (SOURCES) {
SOURCES.forEach((source) => {
source.setAttribute("src", "");
});
}
};
/**
* Update Config
*
*/
const updateConfig = () => {
if (
(config.draggable &&
config.swipeClose &&
!groups[activeGroup].slider.classList.contains(
"tobii__slider--is-draggable"
)) ||
(config.draggable &&
groups[activeGroup].elementsLength > 1 &&
!groups[activeGroup].slider.classList.contains(
"tobii__slider--is-draggable"
))
) {
groups[activeGroup].slider.classList.add("tobii__slider--is-draggable");
}
// Hide buttons if necessary
if (
!config.nav ||
groups[activeGroup].elementsLength === 1 ||
(config.nav === "auto" && isTouchDevice())
) {
prevButton.setAttribute("aria-hidden", "true");
prevButton.disabled = true;
nextButton.setAttribute("aria-hidden", "true");
nextButton.disabled = true;
} else {
prevButton.setAttribute("aria-hidden", "false");
prevButton.disabled = false;
nextButton.setAttribute("aria-hidden", "false");
nextButton.disabled = false;
}
// Hide counter if necessary
if (!config.counter || groups[activeGroup].elementsLength === 1) {
counter.setAttribute("aria-hidden", "true");
} else {
counter.setAttribute("aria-hidden", "false");
}
};
/**
* Update lightbox
*
* @param {string} dir - Current slide direction
*/
const updateLightbox = (dir) => {
updateOffset();
updateCounter();
updateFocus(dir);
};
/**
* Reset Tobii
*
*/
const reset = () => {
if (isOpen()) {
close();
}
// TODO Cleanup
const GROUPS_ENTRIES = Object.entries(groups);
GROUPS_ENTRIES.forEach((groupsEntrie) => {
const SLIDE_ELS = groupsEntrie[1].gallery;
// Remove slides
SLIDE_ELS.forEach((slideEl) => {
remove(slideEl);
});
});
groups = {};
newGroup = activeGroup = null;
figcaptionId = 0;
// TODO
};
/**
* Destroy Tobii
*
*/
const destroy = () => {
reset();
lightbox.parentNode.removeChild(lightbox);
};
/**
* Check if Tobii is open
*
*/
const isOpen = () => {
return lightbox.getAttribute("aria-hidden") === "false";
};
/**
* Detect whether device is touch capable
*
*/
const isTouchDevice = () => {
return "ontouchstart" in window;
};
/**
* Checks whether element's nodeName is part of array
*
*/
const isIgnoreElement = (el) => {
return (
["TEXTAREA", "OPTION", "INPUT", "SELECT"].indexOf(el.nodeName) !== -1 ||
el === prevButton ||
el === nextButton ||
el === closeButton
);
};
/**
* Return current index
*
*/
const slidesIndex = () => {
return groups[activeGroup].currentIndex;
};
/**
* Return elements length
*
*/
const slidesCount = () => {
return groups[activeGroup].elementsLength;
};
/**
* Return current group
*
*/
const currentGroup = () => {
return activeGroup !== null ? activeGroup : newGroup;
};
/**
* Bind events
* @param {String} eventName
* @param {function} callback - callback to call
*
*/
const on = (eventName, callback) => {
lightbox.addEventListener(eventName, callback);
};
/**
* Unbind events
* @param {String} eventName
* @param {function} callback - callback to call
*
*/
const off = (eventName, callback) => {
lightbox.removeEventListener(eventName, callback);
};
init(userOptions);
Tobii.open = open;
Tobii.previous = previous;
Tobii.next = next;
Tobii.close = close;
Tobii.add = checkDependencies;
Tobii.remove = remove;
Tobii.reset = reset;
Tobii.destroy = destroy;
Tobii.isOpen = isOpen;
Tobii.slidesIndex = slidesIndex;
Tobii.select = select;
Tobii.slidesCount = slidesCount;
Tobii.selectGroup = selectGroup;
Tobii.currentGroup = currentGroup;
Tobii.on = on;
Tobii.off = off;
return Tobii;
}
const tobii = new Tobii();
Also see: Tab Triggers