cssAudio - Activefile-genericCSS - ActiveGeneric - ActiveHTML - ActiveImage - ActiveJS - ActiveSVG - ActiveText - Activefile-genericVideo - ActiveLovehtmlicon-new-collectionicon-personicon-teamlog-outoctocatpop-outspinnerstartv

Pen Settings

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

You're using npm packages, so we've auto-selected Babel for you here, which we require to process imports and make it all work. If you need to use a different JavaScript preprocessor, remove the packages in the npm tab.

Add External Scripts/Pens

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.

+ add another resource

Use npm Packages

We can make npm packages available for you to use in your JavaScript. We use webpack to prepare them and make them available to import. We'll also process your JavaScript with Babel.

⚠️ This feature can only be used by logged in users.

Code Indentation

     

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

            
              <p>
  Click a link to show the target image.
  <br>Use the arrow keys to cycle through a category.
  <br>Hit Esc or click somewhere next to the image to hide it.
</p>
<dl>
  <dt>Sports</dt>
  <dd><a href="http://lorempixel.com/400/200/sports/1/" data-tightbox="sports">First</a></dd>
  <dd><a href="http://lorempixel.com/500/200/sports/2/" data-tightbox="sports">Second</a></dd>
  <dd><a href="http://lorempixel.com/400/300/sports/3/" data-tightbox="sports">Third</a></dd>
  <dt>Technics</dt>
  <dd><a href="http://lorempixel.com/400/200/technics/1/" data-tightbox="technics">First</a></dd>
  <dd><a href="http://lorempixel.com/500/200/technics/2/" data-tightbox="technics">Second</a></dd>
  <dd><a href="http://lorempixel.com/400/300/technics/3/" data-tightbox="technics">Third</a></dd>
</dl>
            
          
!
            
              .tightbox-overlay {
  display: none;
  position: fixed;
  background-color: rgba(0, 0, 0, .5);
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  opacity: 0;
}

.tightbox-container {
  position: absolute;
  top: 50%;
  left: 50%;
  border: 5px solid white;
  border-radius: 3px;
}

.tightbox-image {
  display: block;
}

.tightbox-button {
  display: none;
  position: absolute;
  top: 0;
  bottom: 0;
  width: 0;
  height: 0;
  top: 50%;
  margin-top: -2em;
}

.tightbox-button-left {
  left: 2em;
  border-bottom: 2em solid transparent;
  border-top: 2em solid transparent;
  border-right: 4em solid rgba(255, 255, 255, .5);
}

.tightbox-button-right {
  right: 2em;
  border-bottom: 2em solid transparent;
  border-top: 2em solid transparent;
  border-left: 4em solid rgba(255, 255, 255, .5);
}
            
          
!
            
              'use strict'

/////////////////////////////////////////////////////////////////////////////
// This is a image-popup demo to demonstrate some techniques to manipulate //
// the DOM and handle events. Some things (such as the fade-effect) would  //
// better be done using CSS in real life, though.                          //
// MIT @ m3g4p0p                                                           //
/////////////////////////////////////////////////////////////////////////////

///////////////
// Constants //
///////////////

/**
 * Style constants
 * @type {Object}
 */
const STYLE = {
  SHOW: 'block',
  HIDE: 'none'
}

/**
 * Class constants
 */
const CLASS = {
  OVERLAY: 'tightbox-overlay',
  CONTAINER: 'tightbox-container',
  IMAGE: 'tightbox-image',
  BUTTON: {
    DEFAULT: 'tightbox-button',
    LEFT: 'tightbox-button-left',
    RIGHT: 'tightbox-button-right'
  }
}

/**
 * Selector constants
 * @type {Object}
 */
const SELECTOR = {
  TIGHTBOX: '[data-tightbox]'
}

/**
 * Key constants
 * @type {Object}
 */
const KEY = {
  ESC: 27,
  LEFT: 37,
  RIGHT: 39
}

/**
 * Time constants
 * @type {Object}
 */
const TIME = {
  FADE: {
    DEFAULT: 300,
    SHORT: 100
  },
  THROTTLE: 100
}

///////////////////
// HTML elements //
///////////////////

/**
 * Links to trigger the tightbox
 * @type {Nodelist}
 */
const links = document.querySelectorAll(SELECTOR.TIGHTBOX)

/**
 * Overlay element
 * @type {HTMLElement}
 */
const overlay = document.createElement('div')

/**
 * Container element
 * @type {HTMLElement}
 */
const container = document.createElement('div')

/**
 * Image element
 * @type {HTMLElement}
 */
const image = document.createElement('img')

/**
 * Buttom left element
 * @type {HTMLElement}
 */
const buttonLeft = document.createElement('a')

/**
 * Button right element
 * @type {HTMLElement}
 */
const buttonRight = document.createElement('a')

/**
 * Groups of links to cycle through, as defined by the data-tightbox
 * attribute
 * @type {Object}
 */
const groups = Array.from(links).reduce((carry, link) => {
  const group = link.dataset.tightbox

  carry[group] = carry[group] || []
  carry[group].push(link)
  return carry
}, {})

/**
 * Reference to the currently shown image
 * @type {HTMLElement|undefined}
 */
let current

///////////////
// Functions //
///////////////

/**
 * Thunk for binding an event listener once
 * @param  {HTMLElement} element
 * @param  {String} event
 * @param  {Function} callback
 */
const once = (element, event, callback) => {
  element.addEventListener(event, callback, {
    once: true
  })
}

/**
 * Function to throttle the execution of event handlers
 * @param  {Function} callback
 * @param  {Number|undefined} delay
 * @return {Function}
 */
const throttle = (callback, delay) => {
  let hold = false
  delay = delay || TIME.THROTTLE

  return function handleEvent (...args) {
    if (hold) return

    hold = true
    callback.apply(this, args)

    window.setTimeout(() => {
      hold = false
    }, delay)
  }
}

/**
 * Function to fade an element
 * @param  {HTMLElement} element
 * @param  {Number} from
 * @param  {Number} to
 * @param  {Number} [duration=300]
 */
const fade = (element, from, to, duration) => {
  const start = window.performance.now()

  if (from === -1) {
    from = 1 * window
      .getComputedStyle(element)
      .getPropertyValue('opacity')
  }

  duration = duration || TIME.FADE.DEFAULT
  element.style.display = STYLE.SHOW

  window.requestAnimationFrame(function step (timestamp) {
    const progress = timestamp - start
    element.style.opacity = from + (progress / duration) * (to - from)

    if (progress < duration) {
      window.requestAnimationFrame(step)
    } else if (element.style.opacity <= 0) {
      element.style.display = STYLE.HIDE
    }
  })
}

/**
 * Fade in shorthand
 * @param  {HTMLElement} element
 * @param  {Number|undefined} duration
 */
const fadeIn = (element, duration) => {
  fade(element, -1, 1, duration)
}

/**
 * Fade out shorthand
 * @param  {HTMLElement} element
 * @param  {Number|undefined} duration
 */
const fadeOut = (element, duration) => {
  fade(element, -1, 0, duration)
}

/**
 * Function to center a (visible) element to its
 * top/left coordinates
 * @param  {HTMLElement} element
 */
const center = element => {
  Object.assign(element.style, {
    marginLeft: element.offsetWidth / -2 + 'px',
    marginTop: element.offsetHeight / -2 + 'px'
  })
}

/**
 * Hide the tightbox
 * @param  {Event} event
 */
const hideBox = event => {
  fadeOut(overlay)
  document.removeEventListener('keydown', keyHandler)  
  event.preventDefault()    
}

/**
 * Show the previous image in the current group
 * @param  {Event} event
 */
const showPrev = event => {
  const group = groups[current.dataset.tightbox]
  const index = group.indexOf(current)
  const prev = index === 0 ? group.length - 1 : index - 1

  current = group[prev]
  image.src = current.href
  once(image, 'load', () => center(container))
  event.preventDefault()    
}

/**
 * Show the next image in the current group
 * @param  {Event} event
 */
const showNext = event => {
  const group = groups[current.dataset.tightbox]
  const index = group.indexOf(current)
  const next = index === group.length - 1 ? 0 : index + 1

  current = group[next]
  image.src = current.href
  once(image, 'load', () => center(container))  
  event.preventDefault()    
}

/**
 * Callback for the 'keydown' event
 * @param  {Event} event
 */
const keyHandler = event => {
  const group = groups[current.dataset.tightbox]
  const index = group.indexOf(current)

  switch (event.which) {
    case KEY.ESC: {
      hideBox(event)
      break
    }

    case KEY.LEFT: {
      showPrev(event)
      break
    }

    case KEY.RIGHT: {
      showNext(event)
    }
  }
}

////////////////////
// Get it running //
////////////////////

// Initialize the tightbox
buttonLeft.href = buttonRight.href = '#'

overlay.classList.add(CLASS.OVERLAY)
container.classList.add(CLASS.CONTAINER)
image.classList.add(CLASS.IMAGE)
buttonLeft.classList.add(CLASS.BUTTON.DEFAULT, CLASS.BUTTON.LEFT)
buttonRight.classList.add(CLASS.BUTTON.DEFAULT, CLASS.BUTTON.RIGHT)

container.appendChild(image)
container.appendChild(buttonLeft)
container.appendChild(buttonRight)
overlay.appendChild(container)
document.body.appendChild(overlay)

// Hide the tightbox when clicking somewhere on the overlay
overlay.addEventListener('click', event => {
  if (event.target !== overlay) return

  hideBox(event)
})

// Navigate forward/backward in the current group
buttonLeft.addEventListener('click', showPrev)
buttonRight.addEventListener('click', showNext)

// Show the tightbox when clicking a corresponding link
document.addEventListener('click', event => {
  const {target} = event

  if (!target.matches(SELECTOR.TIGHTBOX)) return

  event.preventDefault()
  current = target
  image.src = current.href

  once(image, 'load', () => {
    document.addEventListener('keydown', keyHandler)
    fadeIn(overlay)
    center(container)
  })
})

// Show the navigation buttons when hovering the image
image.addEventListener('mousemove', throttle(event => {
  if (event.offsetX < event.target.offsetWidth / 2) {
    fadeIn(buttonLeft, TIME.FADE.SHORT)
    fadeOut(buttonRight, TIME.FADE.SHORT)
  } else {
    fadeOut(buttonLeft, TIME.FADE.SHORT)
    fadeIn(buttonRight, TIME.FADE.SHORT)
  }
}))

// Hide the navigation buttons when hovering the overlay
// (but not one of its children)
overlay.addEventListener('mouseover', event => {
  if (event.target !== overlay) return

  fadeOut(buttonLeft, TIME.FADE.SHORT)
  fadeOut(buttonRight, TIME.FADE.SHORT)
})
            
          
!
999px
🕑 One or more of the npm packages you are using needs to be built. You're the first person to ever need it! We're building it right now and your preview will start updating again when it's ready.
Loading ..................

Console