[feature] Replace Freezeframe with FreezeframeLite

This commit is contained in:
Cameron Yick 2019-10-07 01:22:04 -04:00 committed by Sean Yesmunt
parent d61e94cede
commit 09f9ef9af0
6 changed files with 281 additions and 11 deletions

View file

@ -0,0 +1,15 @@
// Refined from
// https://github.com/ctrl-freaks/freezeframe.js/tree/master/packages/freezeframe/src
// Names match with constants in styles.scss
export const classes = {
SELECTOR: '.freezeframe',
CONTAINER: 'ff-container',
IMAGE: 'ff-image',
CANVAS: 'ff-canvas',
READY: 'ff-ready',
INACTIVE: 'ff-inactive',
ACTIVE: 'ff-active',
CANVAS_READY: 'ff-canvas-ready',
RESPONSIVE: 'ff-responsive',
};

View file

@ -0,0 +1,172 @@
// This folder is modified based on the original freezeframe@4.0.0-alpha.7
// https://github.com/ctrl-freaks/freezeframe.js/tree/master/packages/freezeframe/src
// Contains subset of features from the main Freezeframe to reduce bundle size.
import imagesLoaded from 'imagesloaded';
import { isTouch, wrapNode, htmlToNode } from './utils';
import * as templates from './templates';
import { classes } from './classes';
import './styles.scss';
const defaultOptions = {
responsive: true,
trigger: 'hover',
overlay: false,
};
const events = {
START: 'start',
STOP: 'stop',
TOGGLE: 'toggle',
};
class FreezeframeLite {
items = [];
$images = [];
eventListeners = {
...Object.values(events).reduce((acc, item) => {
acc[item] = [];
return acc;
}, {}),
};
constructor(node) {
this.options = { ...defaultOptions };
this.init(node);
}
init(node) {
this.isTouch = isTouch();
this.capture(node);
this.load(this.$images);
}
capture(node) {
this.$images = [node];
}
load($images) {
imagesLoaded($images).on('progress', (instance, { img }) => {
this.setup(img);
});
}
async setup($image) {
const freeze = this.wrap($image);
this.items.push(freeze);
await this.process(freeze);
this.attach(freeze);
}
wrap($image) {
const $container = htmlToNode(templates.container());
const $canvas = htmlToNode(templates.canvas());
if (this.options.responsive) {
$container.classList.add(classes.RESPONSIVE);
}
$image.classList.add(classes.IMAGE);
$container.appendChild($canvas);
wrapNode($image, $container);
return {
$container,
$canvas,
$image,
};
}
process(freeze) {
return new Promise(resolve => {
const { $canvas, $image, $container } = freeze;
const { clientWidth, clientHeight } = $image;
$canvas.setAttribute('width', clientWidth);
$canvas.setAttribute('height', clientHeight);
const context = $canvas.getContext('2d');
context.drawImage($image, 0, 0, clientWidth, clientHeight);
$canvas.classList.add(classes.CANVAS_READY);
$canvas.addEventListener(
'transitionend',
() => {
this.ready($container);
resolve(freeze);
},
{
once: true,
}
);
});
}
ready($container) {
$container.classList.add(classes.READY);
$container.classList.add(classes.INACTIVE);
$container.classList.remove(classes.LOADING_ICON);
}
attach(freeze) {
const { $image } = freeze;
if (!this.isTouch) {
$image.addEventListener('mouseenter', () => {
this.toggleOn(freeze);
this.emit(events.START, freeze, true);
this.emit(events.TOGGLE, freeze, true);
});
$image.addEventListener('mouseleave', () => {
this.toggleOff(freeze);
this.emit(events.START, freeze, false);
this.emit(events.TOGGLE, freeze, false);
});
}
}
toggleOff(freeze) {
const { $container } = freeze;
if ($container.classList.contains(classes.READY)) {
$container.classList.add(classes.INACTIVE);
$container.classList.remove(classes.ACTIVE);
}
}
toggleOn(freeze) {
const { $container, $image } = freeze;
if ($container.classList.contains(classes.READY)) {
$image.setAttribute('src', $image.src);
$container.classList.remove(classes.INACTIVE);
$container.classList.add(classes.ACTIVE);
}
}
toggle(freeze) {
const { $container } = freeze;
const isActive = $container.classList.contains(classes.ACTIVE);
if (isActive) {
this.toggleOff(freeze);
} else {
this.toggleOn(freeze);
}
}
emit(event, items, isPlaying) {
this.eventListeners[event].forEach(cb => {
cb(items.length === 1 ? items[0] : items, isPlaying);
});
}
on(event, cb) {
this.eventListeners[event].push(cb);
}
}
export default FreezeframeLite;

View file

@ -0,0 +1,67 @@
// Refined from
// https://github.com/ctrl-freaks/freezeframe.js/tree/master/packages/freezeframe/src
$base-zindex: 0;
$transition-duration: 300ms;
// Classnames must line up with classes in classes.js
.ff-container {
display: inline-block;
position: relative;
.ff-image {
z-index: $base-zindex;
vertical-align: top;
opacity: 0;
}
.ff-canvas {
display: inline-block;
position: absolute;
top: 0;
left: 0;
pointer-events: none;
z-index: $base-zindex + 1;
vertical-align: top;
opacity: 0;
&.ff-canvas-ready {
transition: opacity $transition-duration;
opacity: 1;
}
}
&.ff-active {
.ff-image {
opacity: 1;
}
.ff-canvas.ff-canvas-ready {
transition: none;
opacity: 0;
}
.ff-overlay {
display: none;
}
}
&.ff-inactive {
.ff-canvas.ff-canvas-ready {
transition: opacity $transition-duration;
opacity: 1;
}
.ff-image {
transition: opacity $transition-duration;
transition-delay: 170ms;
opacity: 0;
}
}
&.ff-responsive {
width: 100%;
.ff-image,
.ff-canvas {
width: 100%;
}
}
}

View file

@ -0,0 +1,5 @@
import { classes } from './classes';
export const container = () => `<div class="${classes.CONTAINER} ${classes.LOADING_ICON}"></div>`;
export const canvas = () => `<canvas class="${classes.CANVAS}" width="0" height="0"> </canvas>`;

View file

@ -0,0 +1,18 @@
// Modified from
// https://github.com/ctrl-freaks/freezeframe.js/tree/master/packages/freezeframe/src
export const isTouch = () => {
return 'ontouchstart' in window || 'onmsgesturechange' in window;
};
export const htmlToNode = html => {
const $wrap = window.document.createElement('div');
$wrap.innerHTML = html;
const $content = $wrap.childNodes;
return $content.length > 1 ? $content : $content[0];
};
export const wrapNode = ($el, $wrapper) => {
$el.parentNode.insertBefore($wrapper, $el);
$wrapper.appendChild($el);
};

View file

@ -1,16 +1,16 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import Freezeframe from 'freezeframe';
import Freezeframe from './FreezeframeLite';
const FreezeframeWrapper = props => {
const imgRef = React.useRef();
const freezeframe = React.useRef();
const { src, className, options } = props;
const { src, className } = props;
useEffect(() => {
freezeframe.current = new Freezeframe(imgRef.current, options);
}, [options]);
freezeframe.current = new Freezeframe(imgRef.current);
}, []);
return (
<div className={className}>
@ -22,13 +22,6 @@ const FreezeframeWrapper = props => {
FreezeframeWrapper.propTypes = {
src: PropTypes.string.isRequired,
className: PropTypes.string.isRequired,
// Docs: https://github.com/ctrl-freaks/freezeframe.js/tree/master/packages/freezeframe
options: PropTypes.shape({
selector: PropTypes.string,
trigger: PropTypes.oneOf(['hover', 'click', false]),
overlay: PropTypes.boolean,
responsive: PropTypes.boolean,
}),
};
export default FreezeframeWrapper;