Files
30-seconds-of-code/node_modules/gatsby-image/index.js
2019-08-20 15:52:05 +02:00

629 lines
22 KiB
JavaScript

"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
exports.__esModule = true;
exports.default = void 0;
var _inheritsLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/inheritsLoose"));
var _assertThisInitialized2 = _interopRequireDefault(require("@babel/runtime/helpers/assertThisInitialized"));
var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose"));
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _react = _interopRequireDefault(require("react"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var logDeprecationNotice = function logDeprecationNotice(prop, replacement) {
if (process.env.NODE_ENV === "production") {
return;
}
console.log("\n The \"" + prop + "\" prop is now deprecated and will be removed in the next major version\n of \"gatsby-image\".\n ");
if (replacement) {
console.log("Please use " + replacement + " instead of \"" + prop + "\".");
}
}; // Handle legacy props during their deprecation phase
var convertProps = function convertProps(props) {
var convertedProps = (0, _extends2.default)({}, props);
var resolutions = convertedProps.resolutions,
sizes = convertedProps.sizes,
critical = convertedProps.critical;
if (resolutions) {
convertedProps.fixed = resolutions;
delete convertedProps.resolutions;
}
if (sizes) {
convertedProps.fluid = sizes;
delete convertedProps.sizes;
}
if (critical) {
logDeprecationNotice("critical", "the native \"loading\" attribute");
convertedProps.loading = "eager";
} // convert fluid & fixed to arrays so we only have to work with arrays
if (convertedProps.fluid) {
convertedProps.fluid = groupByMedia([].concat(convertedProps.fluid));
}
if (convertedProps.fixed) {
convertedProps.fixed = groupByMedia([].concat(convertedProps.fixed));
}
return convertedProps;
};
/**
* Find the source of an image to use as a key in the image cache.
* Use `the first image in either `fixed` or `fluid`
* @param {{fluid: {src: string}[], fixed: {src: string}[]}} args
* @return {string}
*/
var getImageSrcKey = function getImageSrcKey(_ref) {
var fluid = _ref.fluid,
fixed = _ref.fixed;
var data = fluid && fluid[0] || fixed && fixed[0];
return data.src;
}; // Cache if we've seen an image before so we don't bother with
// lazy-loading & fading in on subsequent mounts.
var imageCache = Object.create({});
var inImageCache = function inImageCache(props) {
var convertedProps = convertProps(props); // Find src
var src = getImageSrcKey(convertedProps);
return imageCache[src] || false;
};
var activateCacheForImage = function activateCacheForImage(props) {
var convertedProps = convertProps(props); // Find src
var src = getImageSrcKey(convertedProps);
imageCache[src] = true;
}; // Native lazy-loading support: https://addyosmani.com/blog/lazy-loading/
var hasNativeLazyLoadSupport = typeof HTMLImageElement !== "undefined" && "loading" in HTMLImageElement.prototype;
var isBrowser = typeof window !== "undefined";
var hasIOSupport = isBrowser && window.IntersectionObserver;
var io;
var listeners = new WeakMap();
function getIO() {
if (typeof io === "undefined" && typeof window !== "undefined" && window.IntersectionObserver) {
io = new window.IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (listeners.has(entry.target)) {
var cb = listeners.get(entry.target); // Edge doesn't currently support isIntersecting, so also test for an intersectionRatio > 0
if (entry.isIntersecting || entry.intersectionRatio > 0) {
io.unobserve(entry.target);
listeners.delete(entry.target);
cb();
}
}
});
}, {
rootMargin: "200px"
});
}
return io;
}
function generateImageSources(imageVariants) {
return imageVariants.map(function (_ref2) {
var src = _ref2.src,
srcSet = _ref2.srcSet,
srcSetWebp = _ref2.srcSetWebp,
media = _ref2.media,
sizes = _ref2.sizes;
return _react.default.createElement(_react.default.Fragment, {
key: src
}, srcSetWebp && _react.default.createElement("source", {
type: "image/webp",
media: media,
srcSet: srcSetWebp,
sizes: sizes
}), _react.default.createElement("source", {
media: media,
srcSet: srcSet,
sizes: sizes
}));
});
} // Return an array ordered by elements having a media prop, does not use
// native sort, as a stable sort is not guaranteed by all browsers/versions
function groupByMedia(imageVariants) {
var withMedia = [];
var without = [];
imageVariants.forEach(function (variant) {
return (variant.media ? withMedia : without).push(variant);
});
if (without.length > 1 && process.env.NODE_ENV !== "production") {
console.warn("We've found " + without.length + " sources without a media property. They might be ignored by the browser, see: https://www.gatsbyjs.org/packages/gatsby-image/#art-directing-multiple-images");
}
return withMedia.concat(without);
}
function generateTracedSVGSources(imageVariants) {
return imageVariants.map(function (_ref3) {
var src = _ref3.src,
media = _ref3.media,
tracedSVG = _ref3.tracedSVG;
return _react.default.createElement("source", {
key: src,
media: media,
srcSet: tracedSVG
});
});
}
function generateBase64Sources(imageVariants) {
return imageVariants.map(function (_ref4) {
var src = _ref4.src,
media = _ref4.media,
base64 = _ref4.base64;
return _react.default.createElement("source", {
key: src,
media: media,
srcSet: base64
});
});
}
function generateNoscriptSource(_ref5, isWebp) {
var srcSet = _ref5.srcSet,
srcSetWebp = _ref5.srcSetWebp,
media = _ref5.media,
sizes = _ref5.sizes;
var src = isWebp ? srcSetWebp : srcSet;
var mediaAttr = media ? "media=\"" + media + "\" " : "";
var typeAttr = isWebp ? "type='image/webp' " : "";
var sizesAttr = sizes ? "sizes=\"" + sizes + "\" " : "";
return "<source " + typeAttr + mediaAttr + "srcset=\"" + src + "\" " + sizesAttr + "/>";
}
function generateNoscriptSources(imageVariants) {
return imageVariants.map(function (variant) {
return (variant.srcSetWebp ? generateNoscriptSource(variant, true) : "") + generateNoscriptSource(variant);
}).join("");
}
var listenToIntersections = function listenToIntersections(el, cb) {
var observer = getIO();
if (observer) {
observer.observe(el);
listeners.set(el, cb);
}
return function () {
observer.unobserve(el);
listeners.delete(el);
};
};
var noscriptImg = function noscriptImg(props) {
// Check if prop exists before adding each attribute to the string output below to prevent
// HTML validation issues caused by empty values like width="" and height=""
var src = props.src ? "src=\"" + props.src + "\" " : "src=\"\" "; // required attribute
var sizes = props.sizes ? "sizes=\"" + props.sizes + "\" " : "";
var srcSet = props.srcSet ? "srcset=\"" + props.srcSet + "\" " : "";
var title = props.title ? "title=\"" + props.title + "\" " : "";
var alt = props.alt ? "alt=\"" + props.alt + "\" " : "alt=\"\" "; // required attribute
var width = props.width ? "width=\"" + props.width + "\" " : "";
var height = props.height ? "height=\"" + props.height + "\" " : "";
var crossOrigin = props.crossOrigin ? "crossorigin=\"" + props.crossOrigin + "\" " : "";
var loading = props.loading ? "loading=\"" + props.loading + "\" " : "";
var draggable = props.draggable ? "draggable=\"" + props.draggable + "\" " : "";
var sources = generateNoscriptSources(props.imageVariants);
return "<picture>" + sources + "<img " + loading + width + height + sizes + srcSet + src + alt + title + crossOrigin + draggable + "style=\"position:absolute;top:0;left:0;opacity:1;width:100%;height:100%;object-fit:cover;object-position:center\"/></picture>";
}; // Earlier versions of gatsby-image during the 2.x cycle did not wrap
// the `Img` component in a `picture` element. This maintains compatibility
// until a breaking change can be introduced in the next major release
var Placeholder = function Placeholder(_ref6) {
var src = _ref6.src,
imageVariants = _ref6.imageVariants,
generateSources = _ref6.generateSources,
spreadProps = _ref6.spreadProps;
var baseImage = _react.default.createElement(Img, (0, _extends2.default)({
src: src
}, spreadProps));
return imageVariants.length > 1 ? _react.default.createElement("picture", null, generateSources(imageVariants), baseImage) : baseImage;
};
var Img = _react.default.forwardRef(function (props, ref) {
var sizes = props.sizes,
srcSet = props.srcSet,
src = props.src,
style = props.style,
onLoad = props.onLoad,
onError = props.onError,
loading = props.loading,
draggable = props.draggable,
otherProps = (0, _objectWithoutPropertiesLoose2.default)(props, ["sizes", "srcSet", "src", "style", "onLoad", "onError", "loading", "draggable"]);
return _react.default.createElement("img", (0, _extends2.default)({
sizes: sizes,
srcSet: srcSet,
src: src
}, otherProps, {
onLoad: onLoad,
onError: onError,
ref: ref,
loading: loading,
draggable: draggable,
style: (0, _extends2.default)({
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
objectFit: "cover",
objectPosition: "center"
}, style)
}));
});
Img.propTypes = {
style: _propTypes.default.object,
onError: _propTypes.default.func,
onLoad: _propTypes.default.func
};
var Image =
/*#__PURE__*/
function (_React$Component) {
(0, _inheritsLoose2.default)(Image, _React$Component);
function Image(props) {
var _this;
_this = _React$Component.call(this, props) || this; // 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.
_this.seenBefore = isBrowser && inImageCache(props);
_this.addNoScript = !(props.critical && !props.fadeIn);
_this.useIOSupport = !hasNativeLazyLoadSupport && hasIOSupport && !props.critical && !_this.seenBefore;
var isVisible = props.critical || props.loading == "eager" || isBrowser && (hasNativeLazyLoadSupport || !_this.useIOSupport);
_this.state = {
isVisible: isVisible,
imgLoaded: false,
imgCached: false,
fadeIn: !_this.seenBefore && props.fadeIn
};
_this.imageRef = _react.default.createRef();
_this.handleImageLoaded = _this.handleImageLoaded.bind((0, _assertThisInitialized2.default)((0, _assertThisInitialized2.default)(_this)));
_this.handleRef = _this.handleRef.bind((0, _assertThisInitialized2.default)((0, _assertThisInitialized2.default)(_this)));
return _this;
}
var _proto = Image.prototype;
_proto.componentDidMount = function componentDidMount() {
if (this.state.isVisible && typeof this.props.onStartLoad === "function") {
this.props.onStartLoad({
wasCached: inImageCache(this.props)
});
}
if (this.props.critical) {
var img = this.imageRef.current;
if (img && img.complete) {
this.handleImageLoaded();
}
}
};
_proto.componentWillUnmount = function componentWillUnmount() {
if (this.cleanUpListeners) {
this.cleanUpListeners();
}
}; // Specific to IntersectionObserver based lazy-load support
_proto.handleRef = function handleRef(ref) {
var _this2 = this;
if (this.useIOSupport && ref) {
this.cleanUpListeners = listenToIntersections(ref, function () {
var imageInCache = inImageCache(_this2.props);
if (!_this2.state.isVisible && typeof _this2.props.onStartLoad === "function") {
_this2.props.onStartLoad({
wasCached: imageInCache
});
} // imgCached and imgLoaded must update after isVisible,
// Once isVisible is true, imageRef becomes accessible, which imgCached needs access to.
// imgLoaded and imgCached are in a 2nd setState call to be changed together,
// avoiding initiating unnecessary animation frames from style changes.
_this2.setState({
isVisible: true
}, function () {
return _this2.setState({
imgLoaded: imageInCache,
// `currentSrc` should be a string, but can be `undefined` in IE,
// !! operator validates the value is not undefined/null/""
imgCached: !!_this2.imageRef.current.currentSrc
});
});
});
}
};
_proto.handleImageLoaded = function handleImageLoaded() {
activateCacheForImage(this.props);
this.setState({
imgLoaded: true
});
if (this.props.onLoad) {
this.props.onLoad();
}
};
_proto.render = function render() {
var _convertProps = convertProps(this.props),
title = _convertProps.title,
alt = _convertProps.alt,
className = _convertProps.className,
_convertProps$style = _convertProps.style,
style = _convertProps$style === void 0 ? {} : _convertProps$style,
_convertProps$imgStyl = _convertProps.imgStyle,
imgStyle = _convertProps$imgStyl === void 0 ? {} : _convertProps$imgStyl,
_convertProps$placeho = _convertProps.placeholderStyle,
placeholderStyle = _convertProps$placeho === void 0 ? {} : _convertProps$placeho,
placeholderClassName = _convertProps.placeholderClassName,
fluid = _convertProps.fluid,
fixed = _convertProps.fixed,
backgroundColor = _convertProps.backgroundColor,
durationFadeIn = _convertProps.durationFadeIn,
Tag = _convertProps.Tag,
itemProp = _convertProps.itemProp,
loading = _convertProps.loading,
draggable = _convertProps.draggable;
var shouldReveal = this.state.fadeIn === false || this.state.imgLoaded;
var shouldFadeIn = this.state.fadeIn === true && !this.state.imgCached;
var imageStyle = (0, _extends2.default)({
opacity: shouldReveal ? 1 : 0,
transition: shouldFadeIn ? "opacity " + durationFadeIn + "ms" : "none"
}, imgStyle);
var bgColor = typeof backgroundColor === "boolean" ? "lightgray" : backgroundColor;
var delayHideStyle = {
transitionDelay: durationFadeIn + "ms"
};
var imagePlaceholderStyle = (0, _extends2.default)({
opacity: this.state.imgLoaded ? 0 : 1
}, shouldFadeIn && delayHideStyle, imgStyle, placeholderStyle);
var placeholderImageProps = {
title: title,
alt: !this.state.isVisible ? alt : "",
style: imagePlaceholderStyle,
className: placeholderClassName
};
if (fluid) {
var imageVariants = fluid;
var image = imageVariants[0];
return _react.default.createElement(Tag, {
className: (className ? className : "") + " gatsby-image-wrapper",
style: (0, _extends2.default)({
position: "relative",
overflow: "hidden"
}, style),
ref: this.handleRef,
key: "fluid-" + JSON.stringify(image.srcSet)
}, _react.default.createElement(Tag, {
style: {
width: "100%",
paddingBottom: 100 / image.aspectRatio + "%"
}
}), bgColor && _react.default.createElement(Tag, {
title: title,
style: (0, _extends2.default)({
backgroundColor: bgColor,
position: "absolute",
top: 0,
bottom: 0,
opacity: !this.state.imgLoaded ? 1 : 0,
right: 0,
left: 0
}, shouldFadeIn && delayHideStyle)
}), image.base64 && _react.default.createElement(Placeholder, {
src: image.base64,
spreadProps: placeholderImageProps,
imageVariants: imageVariants,
generateSources: generateBase64Sources
}), image.tracedSVG && _react.default.createElement(Placeholder, {
src: image.tracedSVG,
spreadProps: placeholderImageProps,
imageVariants: imageVariants,
generateSources: generateTracedSVGSources
}), this.state.isVisible && _react.default.createElement("picture", null, generateImageSources(imageVariants), _react.default.createElement(Img, {
alt: alt,
title: title,
sizes: image.sizes,
src: image.src,
crossOrigin: this.props.crossOrigin,
srcSet: image.srcSet,
style: imageStyle,
ref: this.imageRef,
onLoad: this.handleImageLoaded,
onError: this.props.onError,
itemProp: itemProp,
loading: loading,
draggable: draggable
})), this.addNoScript && _react.default.createElement("noscript", {
dangerouslySetInnerHTML: {
__html: noscriptImg((0, _extends2.default)({
alt: alt,
title: title,
loading: loading
}, image, {
imageVariants: imageVariants
}))
}
}));
}
if (fixed) {
var _imageVariants = fixed;
var _image = _imageVariants[0];
var divStyle = (0, _extends2.default)({
position: "relative",
overflow: "hidden",
display: "inline-block",
width: _image.width,
height: _image.height
}, style);
if (style.display === "inherit") {
delete divStyle.display;
}
return _react.default.createElement(Tag, {
className: (className ? className : "") + " gatsby-image-wrapper",
style: divStyle,
ref: this.handleRef,
key: "fixed-" + JSON.stringify(_image.srcSet)
}, bgColor && _react.default.createElement(Tag, {
title: title,
style: (0, _extends2.default)({
backgroundColor: bgColor,
width: _image.width,
opacity: !this.state.imgLoaded ? 1 : 0,
height: _image.height
}, shouldFadeIn && delayHideStyle)
}), _image.base64 && _react.default.createElement(Placeholder, {
src: _image.base64,
spreadProps: placeholderImageProps,
imageVariants: _imageVariants,
generateSources: generateBase64Sources
}), _image.tracedSVG && _react.default.createElement(Placeholder, {
src: _image.tracedSVG,
spreadProps: placeholderImageProps,
imageVariants: _imageVariants,
generateSources: generateTracedSVGSources
}), this.state.isVisible && _react.default.createElement("picture", null, generateImageSources(_imageVariants), _react.default.createElement(Img, {
alt: alt,
title: title,
width: _image.width,
height: _image.height,
sizes: _image.sizes,
src: _image.src,
crossOrigin: this.props.crossOrigin,
srcSet: _image.srcSet,
style: imageStyle,
ref: this.imageRef,
onLoad: this.handleImageLoaded,
onError: this.props.onError,
itemProp: itemProp,
loading: loading,
draggable: draggable
})), this.addNoScript && _react.default.createElement("noscript", {
dangerouslySetInnerHTML: {
__html: noscriptImg((0, _extends2.default)({
alt: alt,
title: title,
loading: loading
}, _image, {
imageVariants: _imageVariants
}))
}
}));
}
return null;
};
return Image;
}(_react.default.Component);
Image.defaultProps = {
fadeIn: true,
durationFadeIn: 500,
alt: "",
Tag: "div",
// We set it to `lazy` by default because it's best to default to a performant
// setting and let the user "opt out" to `eager`
loading: "lazy"
};
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,
media: _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,
media: _propTypes.default.string
});
Image.propTypes = {
resolutions: fixedObject,
sizes: fluidObject,
fixed: _propTypes.default.oneOfType([fixedObject, _propTypes.default.arrayOf(fixedObject)]),
fluid: _propTypes.default.oneOfType([fluidObject, _propTypes.default.arrayOf(fluidObject)]),
fadeIn: _propTypes.default.bool,
durationFadeIn: _propTypes.default.number,
title: _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,
crossOrigin: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.bool]),
style: _propTypes.default.object,
imgStyle: _propTypes.default.object,
placeholderStyle: _propTypes.default.object,
placeholderClassName: _propTypes.default.string,
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,
itemProp: _propTypes.default.string,
loading: _propTypes.default.oneOf(["auto", "lazy", "eager"]),
draggable: _propTypes.default.bool
};
var _default = Image;
exports.default = _default;