[feature] Replace Freezeframe with FreezeframeLite
This commit is contained in:
parent
d61e94cede
commit
09f9ef9af0
6 changed files with 281 additions and 11 deletions
15
src/ui/component/cardMedia/FreezeframeLite/classes.js
Normal file
15
src/ui/component/cardMedia/FreezeframeLite/classes.js
Normal 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',
|
||||
};
|
172
src/ui/component/cardMedia/FreezeframeLite/index.js
Normal file
172
src/ui/component/cardMedia/FreezeframeLite/index.js
Normal 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;
|
67
src/ui/component/cardMedia/FreezeframeLite/styles.scss
Normal file
67
src/ui/component/cardMedia/FreezeframeLite/styles.scss
Normal 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%;
|
||||
}
|
||||
}
|
||||
}
|
5
src/ui/component/cardMedia/FreezeframeLite/templates.js
Normal file
5
src/ui/component/cardMedia/FreezeframeLite/templates.js
Normal 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>`;
|
18
src/ui/component/cardMedia/FreezeframeLite/utils.js
Normal file
18
src/ui/component/cardMedia/FreezeframeLite/utils.js
Normal 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);
|
||||
};
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue