UNPKG

glsl-transition-vignette

Version:

customizable GLSL Transition Vignette with hover controls

288 lines (260 loc) 6.93 kB
var React = require("react"); var raf = require("raf"); var extend = require('lodash/object/extend'); var TransitionCanvas = require("./TransitionCanvas"); var TransitionCanvasCache = require("./TransitionCanvasCache"); var ImagePreloaderMixin = require("./ImagePreloaderMixin"); function circular (n, l) { return n < l ? n : 0; } var HOVER = "hover"; var MOUSEDOWN = "mousedown"; var NONE = "none"; var Vignette = React.createClass({ mixins: [ ImagePreloaderMixin ], propTypes: { glsl: React.PropTypes.string.isRequired, images: React.PropTypes.array.isRequired, uniforms: React.PropTypes.object.isRequired, width: React.PropTypes.number.isRequired, height: React.PropTypes.number.isRequired, dpr: React.PropTypes.number, onClick: React.PropTypes.func, autostart: React.PropTypes.bool, defaultProgress: React.PropTypes.number, transitionDuration: React.PropTypes.number, transitionDelay: React.PropTypes.number, transitionEasing: React.PropTypes.func, cursorColor: React.PropTypes.string, controlsMode: React.PropTypes.oneOf([ HOVER, MOUSEDOWN, NONE ]), cache: React.PropTypes.shape({ drawer: React.PropTypes.func.isRequired, resolution: React.PropTypes.number, delay: React.PropTypes.number }), onTransitionPerformed: React.PropTypes.func }, getDefaultProps () { return { controlsMode: HOVER, autostart: false, startonleave: false, defaultProgress: 0.4, onTransitionPerformed: function(){}, transitionDuration: 1500, transitionDelay: 100, transitionEasing: x => x, cursorColor: "#FC6", dpr: window.devicePixelRatio || 1 }; }, getInitialState() { return { hover: false, down: false, hoverProgress: this.props.defaultProgress, progress: this.props.defaultProgress, i: 0, startTime: null }; }, getImageUrlsToPreload() { return this.props.images; }, componentDidMount() { }, componentDidUpdate() { if (!this._running && this.props.autostart && !this.isControlled() && this.preloadImages()) raf(this.update); }, update (t) { if (this.isControlled()) { this._running = false; return; } this._running = true; raf(this.update); const { transitionDuration, transitionDelay, transitionEasing, images } = this.props; const { startTime, progress } = this.state; if (!startTime) this.setState({ startTime: t }); const dt = t-(startTime||t); if (dt > transitionDuration + transitionDelay) { const i = circular(this.state.i+1, images.length); this.setState({ progress: 0, startTime: t, i }); } else if (dt <= transitionDuration) { const p = transitionEasing(dt / transitionDuration); this.setState({ progress: p }); } else if (progress !== 1) { this.setState({ progress: 1 }); } }, isControlled() { const controlsMode = this.props.controlsMode; const { down, hover } = this.state; return controlsMode===HOVER && hover || controlsMode===MOUSEDOWN && down; }, render() { const { width, height, dpr, images, glsl, uniforms, cache, onClick, cursorColor, controlsMode, defaultProgress } = this.props; const { progress, down, hoverProgress, i } = this.state; const length = images.length; const from = this.getPreloadedImage(images[circular(i, length)]); const to = this.getPreloadedImage(images[circular(i+1, length)]); const cursorControlled = this.isControlled(); const p = cursorControlled ? hoverProgress : (!this._running ? defaultProgress : progress); var style = extend({ background: "#000", position: "relative", userSelect: "none", outline: cursorControlled ? "1px solid "+cursorColor : "1px solid #000", width: width+"px", height: height+"px" }, this.props.style); var contentStyle = { position: "absolute", top: 0, left: 0, width: "100%", height: "100%", overflow: "hidden", zIndex: 2 }; var cursorStyle = { display: cursorControlled ? "block" : "none", position: "absolute", top: 0, left: (p * 100)+"%", height: "100%", pointerEvents: "none", zIndex: 1, width: "2px", background: cursorColor, boxShadow: "0px 0px 4px "+cursorColor }; var canvasStyle = { position: "absolute", top: 0, left: 0, width: width+"px", height: height+"px" }; var canvas; if (this.preloadImages()) { canvas = cache ? <TransitionCanvasCache style={canvasStyle} width={width} height={height} dpr={dpr} glsl={glsl} uniforms={uniforms} progress={p} from={from} to={to} drawer={cache.drawer} resolution={cache.resolution} delay={cache.delay} /> : <TransitionCanvas style={canvasStyle} width={width} height={height} dpr={dpr} glsl={glsl} uniforms={uniforms} progress={p} from={from} to={to} />; } var mouseEvents = {}; if (onClick) mouseEvents.onClick = onClick; if (controlsMode === HOVER) { mouseEvents.onMouseEnter = this.onMouseEnter; mouseEvents.onMouseLeave = this.onMouseLeave; mouseEvents.onMouseMove = this.onMouseMove; } else if (controlsMode === MOUSEDOWN) { mouseEvents.onMouseEnter = this.onMouseEnter; mouseEvents.onMouseLeave = this.onMouseLeave; mouseEvents.onMouseDown = this.onMouseDown; if (down) { mouseEvents.onMouseUp = this.onMouseUp; mouseEvents.onMouseMove = this.onMouseMove; } } return <div style={style} {...mouseEvents}> {canvas} <div style={contentStyle}>{this.props.children}</div> <span style={cursorStyle}></span> </div>; }, progressForEvent (e) { var node = this.getDOMNode(); return (e.clientX - node.getBoundingClientRect().left) / node.clientWidth; }, // N.B. following mouse events are only enable under some conditions (see render()) onMouseDown (e) { this.setState({ down: true, hoverProgress: this.progressForEvent(e) }); }, onMouseUp () { this.setState({ down: false }); }, onMouseMove (e) { e.preventDefault(); this.setState({ hoverProgress: this.progressForEvent(e) }); }, onMouseEnter (e) { this.setState({ hover: true, hoverProgress: this.progressForEvent(e) }); }, onMouseLeave () { this.setState({ hover: false, down: false }); } }); export default Vignette;