gatsby-background-image
Version:
Lazy-loading React background-image component with optional support for the blur-up effect.
334 lines (275 loc) • 11.5 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
exports.__esModule = true;
exports.default = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _inheritsLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/inheritsLoose"));
var _assertThisInitialized2 = _interopRequireDefault(require("@babel/runtime/helpers/assertThisInitialized"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _react = _interopRequireDefault(require("react"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _BackgroundUtils = _interopRequireDefault(require("./BackgroundUtils"));
var _HelperUtils = require("./HelperUtils");
var _ImageUtils = require("./ImageUtils");
var _StyleUtils = require("./StyleUtils");
var _IntersectionObserverUtils = require("./IntersectionObserverUtils");
/**
* Main Lazy-loading React background-image component
* with optional support for the blur-up effect.
*/
var BackgroundImage =
/*#__PURE__*/
function (_React$Component) {
(0, _inheritsLoose2.default)(BackgroundImage, _React$Component);
// Needed to prevent handleImageLoaded() firing on gatsby build.
function BackgroundImage(props) {
var _this;
_this = _React$Component.call(this, props) || this; // Default settings for browser without Intersection Observer available.
(0, _defineProperty2.default)((0, _assertThisInitialized2.default)((0, _assertThisInitialized2.default)(_this)), "_isMounted", false);
var isVisible = true;
var imgLoaded = false;
var IOSupported = false;
var fadeIn = props.fadeIn; // If this image has already been loaded before then we can assume it's
// already in the browser cache so it's cheap to just show directly.
var seenBefore = (0, _ImageUtils.inImageCache)(props); // Browser with Intersection Observer available
if (!seenBefore && typeof window !== "undefined" && window.IntersectionObserver) {
isVisible = false;
IOSupported = true;
} // Never render image during SSR
if (typeof window === "undefined") {
isVisible = false;
} // Force render for critical images.
if (props.critical) {
isVisible = true;
IOSupported = false;
} // Check if a noscript element should be included.
var hasNoScript = !(_this.props.critical && !_this.props.fadeIn);
_this.state = {
isVisible: isVisible,
imgLoaded: imgLoaded,
IOSupported: IOSupported,
fadeIn: fadeIn,
hasNoScript: hasNoScript,
seenBefore: seenBefore // Preset backgroundStyles (e.g. during SSR or gatsby build).
};
_this.backgroundStyles = (0, _StyleUtils.presetBackgroundStyles)((0, _BackgroundUtils.default)(_this.props.className)); // Start with an empty background image.
_this.bgImage = "";
_this.handleImageLoaded = _this.handleImageLoaded.bind((0, _assertThisInitialized2.default)((0, _assertThisInitialized2.default)(_this)));
_this.handleRef = _this.handleRef.bind((0, _assertThisInitialized2.default)((0, _assertThisInitialized2.default)(_this))); // "Fake" a reference to an Image loaded via picture element in background.
_this.imageRef = (0, _ImageUtils.createPictureRef)(_this.props, _this.handleImageLoaded);
return _this;
}
var _proto = BackgroundImage.prototype;
_proto.componentDidMount = function componentDidMount() {
this._isMounted = true; // Update background(-*) styles from CSS (e.g. Styled Components).
this.backgroundStyles = (0, _StyleUtils.presetBackgroundStyles)((0, _BackgroundUtils.default)(this.props.className));
if (this.state.isVisible && typeof this.props.onStartLoad === "function") {
this.props.onStartLoad({
wasCached: (0, _ImageUtils.inImageCache)(this.props)
});
}
if (this.props.critical) {
var img = this.imageRef;
if (img && img.complete) {
this.handleImageLoaded();
}
}
};
_proto.componentWillUnmount = function componentWillUnmount() {
this._isMounted = false;
};
_proto.handleRef = function handleRef(ref) {
var _this2 = this;
if (this.state.IOSupported && ref) {
(0, _IntersectionObserverUtils.listenToIntersections)(ref, function () {
var imageInCache = (0, _ImageUtils.inImageCache)(_this2.props);
if (!_this2.state.isVisible && typeof _this2.props.onStartLoad === "function") {
_this2.props.onStartLoad({
wasCached: imageInCache
});
}
_this2.setState({
isVisible: true,
imgLoaded: imageInCache
});
});
}
};
_proto.handleImageLoaded = function handleImageLoaded() {
if (this._isMounted) {
(0, _ImageUtils.activateCacheForImage)(this.props);
this.setState({
imgLoaded: true
});
if (this.state.seenBefore) {
this.setState({
fadeIn: false
});
}
if (this.props.onLoad) {
this.props.onLoad();
}
}
};
_proto.render = function render() {
var _fixOpacity = (0, _StyleUtils.fixOpacity)((0, _HelperUtils.convertProps)(this.props)),
title = _fixOpacity.title,
id = _fixOpacity.id,
alt = _fixOpacity.alt,
className = _fixOpacity.className,
_fixOpacity$style = _fixOpacity.style,
style = _fixOpacity$style === void 0 ? {} : _fixOpacity$style,
fluid = _fixOpacity.fluid,
fixed = _fixOpacity.fixed,
backgroundColor = _fixOpacity.backgroundColor,
Tag = _fixOpacity.Tag,
children = _fixOpacity.children,
_fixOpacity$classId = _fixOpacity.classId,
classId = _fixOpacity$classId === void 0 ? !className ? Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 7) : "" : _fixOpacity$classId;
var bgColor = typeof backgroundColor === "boolean" ? "lightgray" : typeof backgroundColor !== "undefined" ? backgroundColor : "";
var transitionDelay = this.state.imgLoaded ? "0.5s" : "0.25s";
if (fluid) {
var image = fluid; // Set images and visibility according to images available.
var newImageSettings = (0, _ImageUtils.switchImageSettings)({
image: image,
bgImage: this.bgImage,
imageRef: this.imageRef,
isVisible: this.state.isVisible
});
this.bgImage = newImageSettings.bgImage ? newImageSettings.bgImage : newImageSettings.lastImage;
var pseudoStyles = (0, _StyleUtils.createPseudoStyles)((0, _extends2.default)({
classId: classId,
className: className,
transitionDelay: transitionDelay,
bgColor: bgColor,
backgroundStyles: this.backgroundStyles,
style: style,
fadeIn: this.state.fadeIn
}, newImageSettings));
return _react.default.createElement(Tag, {
className: "" + (className ? className : "") + (classId && "gatsby-background-image-" + classId) + " gatsby-image-wrapper",
style: (0, _extends2.default)({
position: "relative",
overflow: "hidden",
opacity: 0.99
}, style, this.backgroundStyles),
id: id,
ref: this.handleRef,
key: "fluid-" + JSON.stringify(image.srcSet)
}, _react.default.createElement("style", {
dangerouslySetInnerHTML: {
__html: pseudoStyles
}
}), this.state.hasNoScript && _react.default.createElement("noscript", {
dangerouslySetInnerHTML: {
__html: (0, _ImageUtils.noscriptImg)((0, _extends2.default)({
alt: alt,
title: title
}, image))
}
}), children);
}
if (fixed) {
var _image = fixed;
var divStyle = (0, _extends2.default)({
position: "relative",
overflow: "hidden",
display: "inline-block",
width: _image.width,
height: _image.height,
opacity: 0.99
}, style);
if (style.display === "inherit") {
delete divStyle.display;
} // Set images and visibility according to images available.
var _newImageSettings = (0, _ImageUtils.switchImageSettings)({
image: _image,
bgImage: this.bgImage,
imageRef: this.imageRef,
isVisible: this.state.isVisible
});
this.bgImage = _newImageSettings.bgImage ? _newImageSettings.bgImage : _newImageSettings.lastImage;
var _pseudoStyles = (0, _StyleUtils.createPseudoStyles)((0, _extends2.default)({
classId: classId,
className: className,
transitionDelay: transitionDelay,
bgColor: bgColor,
backgroundStyles: this.backgroundStyles,
style: style,
fadeIn: this.state.fadeIn
}, _newImageSettings));
return _react.default.createElement(Tag, {
className: "" + (className ? className : "") + (classId ? " gatsby-background-image-" + classId : "") + " gatsby-image-wrapper",
style: (0, _extends2.default)({}, divStyle, this.backgroundStyles),
id: id,
ref: this.handleRef,
key: "fixed-" + JSON.stringify(_image.srcSet)
}, _react.default.createElement("style", {
dangerouslySetInnerHTML: {
__html: _pseudoStyles
}
}), this.state.hasNoScript && _react.default.createElement("noscript", {
dangerouslySetInnerHTML: {
__html: (0, _ImageUtils.noscriptImg)((0, _extends2.default)({
alt: alt,
title: title,
width: _image.width,
height: _image.height
}, _image))
}
}), children);
}
return null;
};
return BackgroundImage;
}(_react.default.Component);
BackgroundImage.defaultProps = {
critical: false,
fadeIn: true,
alt: "",
id: "",
Tag: "div"
};
var fixedObject = _propTypes.default.shape({
width: _propTypes.default.number.isRequired,
height: _propTypes.default.number.isRequired,
src: _propTypes.default.string.isRequired,
srcSet: _propTypes.default.string.isRequired,
base64: _propTypes.default.string,
tracedSVG: _propTypes.default.string,
srcWebp: _propTypes.default.string,
srcSetWebp: _propTypes.default.string
});
var fluidObject = _propTypes.default.shape({
aspectRatio: _propTypes.default.number.isRequired,
src: _propTypes.default.string.isRequired,
srcSet: _propTypes.default.string.isRequired,
sizes: _propTypes.default.string.isRequired,
base64: _propTypes.default.string,
tracedSVG: _propTypes.default.string,
srcWebp: _propTypes.default.string,
srcSetWebp: _propTypes.default.string
});
BackgroundImage.propTypes = {
resolutions: fixedObject,
sizes: fluidObject,
fixed: fixedObject,
fluid: fluidObject,
fadeIn: _propTypes.default.bool,
title: _propTypes.default.string,
id: _propTypes.default.string,
alt: _propTypes.default.string,
className: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.object]),
// Support Glamor's css prop.
critical: _propTypes.default.bool,
style: _propTypes.default.oneOfType([_propTypes.default.object, _propTypes.default.array]),
// Using PropTypes from RN.
backgroundColor: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.bool]),
onLoad: _propTypes.default.func,
onError: _propTypes.default.func,
onStartLoad: _propTypes.default.func,
Tag: _propTypes.default.string,
classId: _propTypes.default.string
};
var _default = BackgroundImage;
exports.default = _default;