[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 React, { useEffect } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Freezeframe from 'freezeframe';
|
import Freezeframe from './FreezeframeLite';
|
||||||
|
|
||||||
const FreezeframeWrapper = props => {
|
const FreezeframeWrapper = props => {
|
||||||
const imgRef = React.useRef();
|
const imgRef = React.useRef();
|
||||||
const freezeframe = React.useRef();
|
const freezeframe = React.useRef();
|
||||||
|
|
||||||
const { src, className, options } = props;
|
const { src, className } = props;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
freezeframe.current = new Freezeframe(imgRef.current, options);
|
freezeframe.current = new Freezeframe(imgRef.current);
|
||||||
}, [options]);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
|
@ -22,13 +22,6 @@ const FreezeframeWrapper = props => {
|
||||||
FreezeframeWrapper.propTypes = {
|
FreezeframeWrapper.propTypes = {
|
||||||
src: PropTypes.string.isRequired,
|
src: PropTypes.string.isRequired,
|
||||||
className: 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;
|
export default FreezeframeWrapper;
|
||||||
|
|
Loading…
Add table
Reference in a new issue