UNPKG

@story-telling-reporter/react-scroll-to-audio

Version:

## Create Embed Code See [@story-telling-reporter/react-embed-code-generator](https://github.com/nickhsine/story-telling-reporter/blob/main/packages/embed-code-generator/README.md) for more information.

311 lines (300 loc) 12.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ScrollToAudio = ScrollToAudio; exports.buildBottomEntryPointStaticMarkup = buildBottomEntryPointStaticMarkup; var _react = _interopRequireWildcard(require("react")); var _reactDom = require("react-dom"); var _styledComponents = _interopRequireDefault(require("styled-components")); var _icons = require("./icons"); var _debounce = _interopRequireDefault(require("lodash/debounce")); var _reactUiToolkit = require("@story-telling-reporter/react-ui-toolkit"); var _jsxRuntime = require("react/jsx-runtime"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } const { Hint } = _reactUiToolkit.twreporter; const _ = { debounce: _debounce.default }; function ScrollToAudio({ id = 'scroll-to-audio-id', audioUrls, className, preload = 'auto', hintOnly = false, hintId }) { const audioRef = (0, _react.useRef)(null); const [muted, setMuted] = _reactUiToolkit.hooks.useMuted(true, audioRef); const topEntryPointRef = (0, _react.useRef)(null); const bottomEntryPointRef = (0, _react.useRef)(null); const [paused, setPaused] = (0, _react.useState)(true); const [mounted, setMounted] = (0, _react.useState)(false); const [hideMuteButton, setHideMuteButton] = (0, _react.useState)(true); // hide mute button initially (0, _react.useEffect)(() => { setMounted(true); }, []); (0, _react.useEffect)(() => { if (hintOnly) { return; } const handleScroll = _.debounce(() => { const topEntryElement = topEntryPointRef.current; const bottomEntryElement = bottomEntryPointRef.current; if (!topEntryElement || topEntryElement.getBoundingClientRect().width == 0 || topEntryElement.getBoundingClientRect().height == 0) { console.log(`[react-scroll-to-audio][${id}] \`topEntryElement\` is not available. Remove scroll event listener.`); window.removeEventListener('scroll', handleScroll); return; } const viewportHeight = window.innerHeight; const rootMargin = Math.ceil(viewportHeight * 0.25); const topEntryY = topEntryElement.getBoundingClientRect().y; // top entry point is below viewport // which means element is outside viewport bottom if (topEntryY > viewportHeight - rootMargin) { setHideMuteButton(true); setPaused(true); return; } let bottomEntryY = 0; // bottom entry point is not existed // give it a default value: top entry + 100vh if (!bottomEntryElement) { bottomEntryY = topEntryY + viewportHeight; } else { bottomEntryY = bottomEntryElement.getBoundingClientRect().y; } // bottom entry point is above viewport top, // which means element is outside viewport if (bottomEntryY < 0 + rootMargin) { setHideMuteButton(true); setPaused(true); } // top entry point is in the viewport or above viewport bottom // AND // bottom entry point is in the viewport or below viewport top // which means element is inside viewport if (topEntryY < viewportHeight - rootMargin && bottomEntryY > 0 + rootMargin) { setHideMuteButton(false); if (!muted) { // do not play audio since it's muted setPaused(false); } } }, 50); console.log(`[react-scroll-to-audio][${id}] add scroll event listener. \`muted\` state is ${muted}`); window.addEventListener('scroll', handleScroll); return () => { console.log(`[react-scroll-to-audio][${id}] useEffect cleanup function. Remove scroll event listener.`); window.removeEventListener('scroll', handleScroll); }; }, [muted, hintOnly]); // set audio muted attribute according to browser muted state (0, _react.useEffect)(() => { const audioElement = audioRef.current; if (!audioElement) { return; } audioElement.muted = muted; }, [muted]); (0, _react.useEffect)(() => { const audioElement = audioRef.current; if (!audioElement) { return; } if (paused) { audioElement.pause(); console.log(`[react-scroll-to-audio][${id}] audio paused.`); } else { const startPlayPromise = audioElement.play(); if (startPlayPromise !== undefined) { startPlayPromise // play successfully .then(() => { console.log(`[react-scroll-to-audio][${id}] audio plays successfully.`); audioElement.setAttribute('data-played', 'true'); }) // fail to play .catch(error => { // browser prevent from playing audio before user interactions console.log(`[react-scroll-to-audio][${id}] unable to play audio`); console.log(`[react-scroll-to-audio][${id}] error: `, error); // pause and mute audio since browser does not allow to play it setPaused(true); setMuted(true); }); } } }, [paused]); const onMuteButtonClick = () => { const nextMuted = !muted; setMuted(nextMuted); // pause audio if muted, otherwise play the audio setPaused(nextMuted); }; const bottomEntryId = id + '-bottom-entry-point'; let buttonJsx = null; let bottomEntryPlaceholder = null; if (mounted) { bottomEntryPlaceholder = document.getElementById(bottomEntryId); const mobileButtonJsx = /*#__PURE__*/(0, _jsxRuntime.jsx)(MobileOnly, { children: /*#__PURE__*/(0, _jsxRuntime.jsx)(MuteButtonWithMobileToolBar, { className: "scroll-to-audio-muted-button", $hide: hideMuteButton, onClick: onMuteButtonClick, children: muted ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_icons.MuteIcon, {}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_icons.SoundIcon, {}) }) }); const desktopButtonJsx = /*#__PURE__*/(0, _jsxRuntime.jsx)(DesktopOnly, { children: /*#__PURE__*/(0, _jsxRuntime.jsx)(FixedMuteButton, { className: "scroll-to-audio-muted-button", $hide: hideMuteButton, onClick: onMuteButtonClick, children: muted ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_icons.MuteIcon, {}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_icons.SoundIcon, {}) }) }); buttonJsx = /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, { children: [mobileButtonJsx, desktopButtonJsx] }); } if (hintOnly) { return /*#__PURE__*/(0, _jsxRuntime.jsx)(Hint, { id: hintId }); } const audioJsx = /*#__PURE__*/(0, _jsxRuntime.jsx)("audio", { ref: audioRef, preload: preload, "data-played": false, "data-paused": paused, "data-twreporter-story-telling": true, "data-muted": muted, style: { display: 'none' }, playsInline: true, loop: true, children: audioUrls.map((url, index) => /*#__PURE__*/(0, _jsxRuntime.jsx)("source", { src: url }, `audio_source_${index}`)) }); return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, { children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(TopEntryContainer, { "data-twreporter-story-telling": true, "data-react-scroll-to-audio": true, "data-id": `${id}-top-entry-point`, "data-top-entry": true, className: className, ref: topEntryPointRef, children: audioJsx }), bottomEntryPlaceholder && /*#__PURE__*/(0, _reactDom.createPortal)( /*#__PURE__*/(0, _jsxRuntime.jsx)(BottomEntryContainer, { "data-twreporter-story-telling": true, "data-react-scroll-to-audio": true, "data-id": bottomEntryId, "data-bottom-entry": true, ref: bottomEntryPointRef }), bottomEntryPlaceholder), buttonJsx] }); } function buildBottomEntryPointStaticMarkup({ id = 'scroll-to-audio-id' }) { const bottomEntryId = id + '-bottom-entry-point'; return `<div id="${bottomEntryId}"></div>`; } const TopEntryContainer = _styledComponents.default.div.withConfig({ displayName: "src__TopEntryContainer", componentId: "sc-1rtlo6l-0" })(["min-height:10px;"]); const BottomEntryContainer = _styledComponents.default.div.withConfig({ displayName: "src__BottomEntryContainer", componentId: "sc-1rtlo6l-1" })(["min-height:10px;"]); const MuteButton = _styledComponents.default.div.withConfig({ displayName: "src__MuteButton", componentId: "sc-1rtlo6l-2" })(["height:40px;width:40px;border-radius:100%;background-color:#00000040;display:flex;cursor:pointer;> svg{width:20px;height:20px;margin:auto;fill:white;}&:hover{background-color:#00000080;}"]); const MuteButtonWithMobileToolBar = (0, _styledComponents.default)(MuteButton).withConfig({ displayName: "src__MuteButtonWithMobileToolBar", componentId: "sc-1rtlo6l-3" })(["position:fixed;bottom:calc(40px + 48px);left:calc(50vw + 50% - 40px - 16px);", " transition:transform 300ms ease-in-out;"], props => { return props !== null && props !== void 0 && props.$hide ? `transform: translateY(150px);` // slide out the viewport : `transform: translateY(0);`; }); const FixedMuteButton = (0, _styledComponents.default)(MuteButton).withConfig({ displayName: "src__FixedMuteButton", componentId: "sc-1rtlo6l-4" })(["position:fixed;bottom:16px;right:16px;z-index:800;", " transition:transform 300ms ease-in-out;"], props => { return props !== null && props !== void 0 && props.$hide ? 'transform: translateY(calc((40px + 16px) * 2));' // slide out the viewport : 'transform: translateY(0);'; }); const MobileOnly = _styledComponents.default.div.withConfig({ displayName: "src__MobileOnly", componentId: "sc-1rtlo6l-5" })(["display:none;@media (max-width:1023px){display:block;}"]); const DesktopOnly = _styledComponents.default.div.withConfig({ displayName: "src__DesktopOnly", componentId: "sc-1rtlo6l-6" })(["display:none;@media (min-width:1024px){display:block;}"]); //function fadeOut( // audioElement: HTMLVideoElement, // targetVolume: number, // duration: number //) { // // make sure volume is between 0 to 1 // targetVolume = Math.max(0, Math.min(1, targetVolume)) // // decrease volume step by step // const volumeDecreaseStep = // (audioElement.volume - targetVolume) / (duration / 100) // const interval = setInterval(() => { // if (audioElement.volume > targetVolume + volumeDecreaseStep) { // audioElement.volume -= volumeDecreaseStep // } else { // // decrease to the target volume // audioElement.volume = targetVolume // audioElement.pause() // console.log('[react-scroll-to-audio] audio paused.') // clearInterval(interval) // } // }, 100) //} //function fadeIn( // audioElement: HTMLVideoElement, // targetVolume: number, // duration: number //) { // // make sure volume is between 0 to 1 // targetVolume = Math.max(0, Math.min(1, targetVolume)) // // increase volume step by step // const volumeIncreaseStep = targetVolume / (duration / 100) // audioElement.volume = 0 // start audio with 0 volume // // play the audio // const startPlayPromise = audioElement.play() // if (startPlayPromise !== undefined) { // startPlayPromise // // play successfully // .then(() => { // console.log('[react-scroll-to-audio] audio plays successfully.') // audioElement.setAttribute('data-played', 'true') // const interval = setInterval(() => { // if (audioElement.volume < targetVolume - volumeIncreaseStep) { // audioElement.volume += volumeIncreaseStep // } else { // // increase to the target volume // audioElement.volume = targetVolume // clearInterval(interval) // } // }, 100) // }) // // fail to play // .catch((error) => { // // browser prevent from playing audio before user interactions // console.log('[react-scroll-to-audio] unable to play audio') // console.log('[react-scroll-to-audio] error: ', error) // }) // } //}