2020-04-23 13:31:39 -07:00
|
|
|
import React from "react";
|
2020-09-13 04:12:14 -07:00
|
|
|
import { Box, DarkMode, Flex, Text } from "@chakra-ui/core";
|
2020-07-20 21:32:42 -07:00
|
|
|
import { WarningIcon } from "@chakra-ui/icons";
|
2020-09-21 18:50:27 -07:00
|
|
|
import { css, cx } from "emotion";
|
|
|
|
import { CSSTransition, TransitionGroup } from "react-transition-group";
|
2020-04-24 19:16:24 -07:00
|
|
|
|
simplify canvas code, just use separate elements
Previously I tried to be clever and pre-optimize by putting all the layers onto one canvas… I think this probably helped by batching their paints, but it made fades less smooth by not taking advantage of native CSS transitions, and it made us dip into JS way more often than necessary.
Here, I take the simpler approach: just layers of <img> and <canvas> tags, with each animated layer on its own canvas, and letting the browser handle transitions and compositing, and separate `setInterval` timers to manage their framerates.
I have a suspicion that batching the paints could help performance more, but honestly, maybe that batching is already happening somehow, because things look pretty great on my big-screen stress test now; and so if it _is_ relevant, I want to wait and see after testing on low-power devices.
2020-10-08 04:13:47 -07:00
|
|
|
import OutfitMovieLayer, {
|
|
|
|
buildMovieClip,
|
|
|
|
hasAnimations,
|
2020-09-22 01:44:24 -07:00
|
|
|
loadImage,
|
simplify canvas code, just use separate elements
Previously I tried to be clever and pre-optimize by putting all the layers onto one canvas… I think this probably helped by batching their paints, but it made fades less smooth by not taking advantage of native CSS transitions, and it made us dip into JS way more often than necessary.
Here, I take the simpler approach: just layers of <img> and <canvas> tags, with each animated layer on its own canvas, and letting the browser handle transitions and compositing, and separate `setInterval` timers to manage their framerates.
I have a suspicion that batching the paints could help performance more, but honestly, maybe that batching is already happening somehow, because things look pretty great on my big-screen stress test now; and so if it _is_ relevant, I want to wait and see after testing on low-power devices.
2020-10-08 04:13:47 -07:00
|
|
|
loadMovieLibrary,
|
2020-09-22 01:44:24 -07:00
|
|
|
useEaselDependenciesLoader,
|
simplify canvas code, just use separate elements
Previously I tried to be clever and pre-optimize by putting all the layers onto one canvas… I think this probably helped by batching their paints, but it made fades less smooth by not taking advantage of native CSS transitions, and it made us dip into JS way more often than necessary.
Here, I take the simpler approach: just layers of <img> and <canvas> tags, with each animated layer on its own canvas, and letting the browser handle transitions and compositing, and separate `setInterval` timers to manage their framerates.
I have a suspicion that batching the paints could help performance more, but honestly, maybe that batching is already happening somehow, because things look pretty great on my big-screen stress test now; and so if it _is_ relevant, I want to wait and see after testing on low-power devices.
2020-10-08 04:13:47 -07:00
|
|
|
} from "./OutfitMovieLayer";
|
2020-07-22 21:29:57 -07:00
|
|
|
import HangerSpinner from "./HangerSpinner";
|
simplify canvas code, just use separate elements
Previously I tried to be clever and pre-optimize by putting all the layers onto one canvas… I think this probably helped by batching their paints, but it made fades less smooth by not taking advantage of native CSS transitions, and it made us dip into JS way more often than necessary.
Here, I take the simpler approach: just layers of <img> and <canvas> tags, with each animated layer on its own canvas, and letting the browser handle transitions and compositing, and separate `setInterval` timers to manage their framerates.
I have a suspicion that batching the paints could help performance more, but honestly, maybe that batching is already happening somehow, because things look pretty great on my big-screen stress test now; and so if it _is_ relevant, I want to wait and see after testing on low-power devices.
2020-10-08 04:13:47 -07:00
|
|
|
import { safeImageUrl, useLocalStorage } from "../util";
|
2020-05-02 13:40:37 -07:00
|
|
|
import useOutfitAppearance from "./useOutfitAppearance";
|
2020-04-24 23:13:28 -07:00
|
|
|
|
2020-05-02 13:40:37 -07:00
|
|
|
/**
|
2020-07-22 21:29:57 -07:00
|
|
|
* OutfitPreview is for rendering a full outfit! It accepts outfit data,
|
|
|
|
* fetches the appearance data for it, and preloads and renders the layers
|
|
|
|
* together.
|
|
|
|
*
|
2020-07-22 22:07:45 -07:00
|
|
|
* If the species/color/pose fields are null and a `placeholder` node is
|
|
|
|
* provided instead, we'll render the placeholder. And then, once those props
|
|
|
|
* become non-null, we'll keep showing the placeholder below the loading
|
|
|
|
* overlay until loading completes. (We use this on the homepage to show the
|
|
|
|
* beach splash until outfit data arrives!)
|
|
|
|
*
|
2020-07-22 21:29:57 -07:00
|
|
|
* TODO: There's some duplicate work happening in useOutfitAppearance and
|
|
|
|
* useOutfitState both getting appearance data on first load...
|
2020-05-02 13:40:37 -07:00
|
|
|
*/
|
2020-07-22 22:15:07 -07:00
|
|
|
function OutfitPreview({
|
|
|
|
speciesId,
|
|
|
|
colorId,
|
|
|
|
pose,
|
|
|
|
wornItemIds,
|
2020-09-13 04:12:14 -07:00
|
|
|
appearanceId = null,
|
2020-09-21 02:43:58 -07:00
|
|
|
isLoading = false,
|
2020-07-22 22:15:07 -07:00
|
|
|
placeholder,
|
2020-09-13 04:12:14 -07:00
|
|
|
loadingDelayMs,
|
|
|
|
spinnerVariant,
|
2020-09-24 06:13:27 -07:00
|
|
|
onChangeHasAnimations = null,
|
2020-07-22 22:15:07 -07:00
|
|
|
}) {
|
2020-07-22 21:29:57 -07:00
|
|
|
const { loading, error, visibleLayers } = useOutfitAppearance({
|
|
|
|
speciesId,
|
|
|
|
colorId,
|
|
|
|
pose,
|
2020-08-28 22:58:39 -07:00
|
|
|
appearanceId,
|
2020-07-22 21:29:57 -07:00
|
|
|
wornItemIds,
|
|
|
|
});
|
2020-04-25 07:22:03 -07:00
|
|
|
|
simplify canvas code, just use separate elements
Previously I tried to be clever and pre-optimize by putting all the layers onto one canvas… I think this probably helped by batching their paints, but it made fades less smooth by not taking advantage of native CSS transitions, and it made us dip into JS way more often than necessary.
Here, I take the simpler approach: just layers of <img> and <canvas> tags, with each animated layer on its own canvas, and letting the browser handle transitions and compositing, and separate `setInterval` timers to manage their framerates.
I have a suspicion that batching the paints could help performance more, but honestly, maybe that batching is already happening somehow, because things look pretty great on my big-screen stress test now; and so if it _is_ relevant, I want to wait and see after testing on low-power devices.
2020-10-08 04:13:47 -07:00
|
|
|
const {
|
|
|
|
loading: loading2,
|
|
|
|
error: error2,
|
|
|
|
loadedLayers,
|
|
|
|
layersHaveAnimations,
|
|
|
|
} = usePreloadLayers(visibleLayers);
|
|
|
|
|
|
|
|
const [isPaused] = useLocalStorage("DTIOutfitIsPaused", true);
|
|
|
|
|
|
|
|
React.useEffect(() => {
|
2020-10-08 04:20:23 -07:00
|
|
|
if (onChangeHasAnimations) {
|
|
|
|
onChangeHasAnimations(layersHaveAnimations);
|
|
|
|
}
|
simplify canvas code, just use separate elements
Previously I tried to be clever and pre-optimize by putting all the layers onto one canvas… I think this probably helped by batching their paints, but it made fades less smooth by not taking advantage of native CSS transitions, and it made us dip into JS way more often than necessary.
Here, I take the simpler approach: just layers of <img> and <canvas> tags, with each animated layer on its own canvas, and letting the browser handle transitions and compositing, and separate `setInterval` timers to manage their framerates.
I have a suspicion that batching the paints could help performance more, but honestly, maybe that batching is already happening somehow, because things look pretty great on my big-screen stress test now; and so if it _is_ relevant, I want to wait and see after testing on low-power devices.
2020-10-08 04:13:47 -07:00
|
|
|
}, [layersHaveAnimations, onChangeHasAnimations]);
|
2020-06-05 23:56:42 -07:00
|
|
|
|
2020-07-22 21:29:57 -07:00
|
|
|
if (error || error2) {
|
2020-04-23 13:31:39 -07:00
|
|
|
return (
|
|
|
|
<FullScreenCenter>
|
2020-09-10 03:06:44 -07:00
|
|
|
<Text color="green.50" d="flex" alignItems="center">
|
2020-07-20 21:32:42 -07:00
|
|
|
<WarningIcon />
|
2020-04-23 13:31:39 -07:00
|
|
|
<Box width={2} />
|
|
|
|
Could not load preview. Try again?
|
|
|
|
</Text>
|
|
|
|
</FullScreenCenter>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-05-02 22:59:30 -07:00
|
|
|
return (
|
2020-09-13 04:12:14 -07:00
|
|
|
<OutfitLayers
|
2020-09-21 02:43:58 -07:00
|
|
|
loading={isLoading || loading || loading2}
|
2020-09-13 04:12:14 -07:00
|
|
|
visibleLayers={loadedLayers}
|
|
|
|
placeholder={placeholder}
|
|
|
|
loadingDelayMs={loadingDelayMs}
|
|
|
|
spinnerVariant={spinnerVariant}
|
2020-09-24 06:13:27 -07:00
|
|
|
onChangeHasAnimations={onChangeHasAnimations}
|
2020-09-21 18:50:27 -07:00
|
|
|
doTransitions
|
simplify canvas code, just use separate elements
Previously I tried to be clever and pre-optimize by putting all the layers onto one canvas… I think this probably helped by batching their paints, but it made fades less smooth by not taking advantage of native CSS transitions, and it made us dip into JS way more often than necessary.
Here, I take the simpler approach: just layers of <img> and <canvas> tags, with each animated layer on its own canvas, and letting the browser handle transitions and compositing, and separate `setInterval` timers to manage their framerates.
I have a suspicion that batching the paints could help performance more, but honestly, maybe that batching is already happening somehow, because things look pretty great on my big-screen stress test now; and so if it _is_ relevant, I want to wait and see after testing on low-power devices.
2020-10-08 04:13:47 -07:00
|
|
|
isPaused={isPaused}
|
2020-09-13 04:12:14 -07:00
|
|
|
/>
|
2020-05-02 22:59:30 -07:00
|
|
|
);
|
2020-05-02 21:04:54 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* OutfitLayers is the raw UI component for rendering outfit layers. It's
|
|
|
|
* used both in the main outfit preview, and in other minor UIs!
|
|
|
|
*/
|
2020-07-22 22:07:45 -07:00
|
|
|
export function OutfitLayers({
|
|
|
|
loading,
|
|
|
|
visibleLayers,
|
|
|
|
placeholder,
|
2020-09-13 04:12:14 -07:00
|
|
|
loadingDelayMs = 500,
|
|
|
|
spinnerVariant = "overlay",
|
2020-09-21 18:50:27 -07:00
|
|
|
doTransitions = false,
|
2020-09-24 06:13:27 -07:00
|
|
|
onChangeHasAnimations = null,
|
simplify canvas code, just use separate elements
Previously I tried to be clever and pre-optimize by putting all the layers onto one canvas… I think this probably helped by batching their paints, but it made fades less smooth by not taking advantage of native CSS transitions, and it made us dip into JS way more often than necessary.
Here, I take the simpler approach: just layers of <img> and <canvas> tags, with each animated layer on its own canvas, and letting the browser handle transitions and compositing, and separate `setInterval` timers to manage their framerates.
I have a suspicion that batching the paints could help performance more, but honestly, maybe that batching is already happening somehow, because things look pretty great on my big-screen stress test now; and so if it _is_ relevant, I want to wait and see after testing on low-power devices.
2020-10-08 04:13:47 -07:00
|
|
|
isPaused = true,
|
2020-07-22 22:07:45 -07:00
|
|
|
}) {
|
2020-09-21 18:11:25 -07:00
|
|
|
const containerRef = React.useRef(null);
|
|
|
|
const [canvasSize, setCanvasSize] = React.useState(0);
|
2020-09-13 04:12:14 -07:00
|
|
|
const [loadingDelayHasPassed, setLoadingDelayHasPassed] = React.useState(
|
|
|
|
false
|
|
|
|
);
|
|
|
|
|
2020-09-22 01:44:24 -07:00
|
|
|
const { loading: loadingEasel } = useEaselDependenciesLoader();
|
|
|
|
const loadingAnything = loading || loadingEasel;
|
|
|
|
|
2020-09-21 19:03:17 -07:00
|
|
|
// When we start in a loading state, or re-enter a loading state, start the
|
|
|
|
// loading delay timer.
|
2020-09-13 04:12:14 -07:00
|
|
|
React.useEffect(() => {
|
2020-09-22 01:44:24 -07:00
|
|
|
if (loadingAnything) {
|
2020-09-21 19:03:17 -07:00
|
|
|
setLoadingDelayHasPassed(false);
|
|
|
|
const t = setTimeout(
|
|
|
|
() => setLoadingDelayHasPassed(true),
|
|
|
|
loadingDelayMs
|
|
|
|
);
|
|
|
|
return () => clearTimeout(t);
|
|
|
|
}
|
2020-09-22 01:44:24 -07:00
|
|
|
}, [loadingDelayMs, loadingAnything]);
|
2020-07-22 22:07:45 -07:00
|
|
|
|
2020-09-21 18:11:25 -07:00
|
|
|
React.useLayoutEffect(() => {
|
2020-09-24 08:29:56 -07:00
|
|
|
function computeAndSaveCanvasSize() {
|
2020-09-21 18:11:25 -07:00
|
|
|
setCanvasSize(
|
|
|
|
Math.min(
|
|
|
|
containerRef.current.offsetWidth,
|
|
|
|
containerRef.current.offsetHeight
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-09-24 08:29:56 -07:00
|
|
|
computeAndSaveCanvasSize();
|
|
|
|
window.addEventListener("resize", computeAndSaveCanvasSize);
|
|
|
|
return () => window.removeEventListener("resize", computeAndSaveCanvasSize);
|
2020-09-21 18:11:25 -07:00
|
|
|
}, [setCanvasSize]);
|
|
|
|
|
2020-04-23 15:19:36 -07:00
|
|
|
return (
|
2020-08-01 01:35:27 -07:00
|
|
|
<Box
|
|
|
|
pos="relative"
|
|
|
|
height="100%"
|
|
|
|
width="100%"
|
|
|
|
// Create a stacking context, so the z-indexed layers don't escape!
|
|
|
|
zIndex="0"
|
2020-09-21 18:11:25 -07:00
|
|
|
ref={containerRef}
|
2020-08-01 01:35:27 -07:00
|
|
|
>
|
2020-07-22 22:07:45 -07:00
|
|
|
{placeholder && (
|
|
|
|
<FullScreenCenter>
|
|
|
|
<Box
|
|
|
|
// We show the placeholder until there are visible layers, at which
|
|
|
|
// point we fade it out.
|
|
|
|
opacity={visibleLayers.length === 0 ? 1 : 0}
|
|
|
|
transition="opacity 0.2s"
|
|
|
|
>
|
|
|
|
{placeholder}
|
|
|
|
</Box>
|
|
|
|
</FullScreenCenter>
|
|
|
|
)}
|
simplify canvas code, just use separate elements
Previously I tried to be clever and pre-optimize by putting all the layers onto one canvas… I think this probably helped by batching their paints, but it made fades less smooth by not taking advantage of native CSS transitions, and it made us dip into JS way more often than necessary.
Here, I take the simpler approach: just layers of <img> and <canvas> tags, with each animated layer on its own canvas, and letting the browser handle transitions and compositing, and separate `setInterval` timers to manage their framerates.
I have a suspicion that batching the paints could help performance more, but honestly, maybe that batching is already happening somehow, because things look pretty great on my big-screen stress test now; and so if it _is_ relevant, I want to wait and see after testing on low-power devices.
2020-10-08 04:13:47 -07:00
|
|
|
<TransitionGroup enter={false} exit={doTransitions}>
|
|
|
|
{visibleLayers.map((layer) => (
|
|
|
|
<CSSTransition
|
|
|
|
// We manage the fade-in and fade-out separately! The fade-out
|
|
|
|
// happens here, when the layer exits the DOM.
|
|
|
|
key={layer.id}
|
|
|
|
classNames={css`
|
|
|
|
&-exit {
|
|
|
|
opacity: 1;
|
|
|
|
}
|
2020-09-21 18:50:27 -07:00
|
|
|
|
simplify canvas code, just use separate elements
Previously I tried to be clever and pre-optimize by putting all the layers onto one canvas… I think this probably helped by batching their paints, but it made fades less smooth by not taking advantage of native CSS transitions, and it made us dip into JS way more often than necessary.
Here, I take the simpler approach: just layers of <img> and <canvas> tags, with each animated layer on its own canvas, and letting the browser handle transitions and compositing, and separate `setInterval` timers to manage their framerates.
I have a suspicion that batching the paints could help performance more, but honestly, maybe that batching is already happening somehow, because things look pretty great on my big-screen stress test now; and so if it _is_ relevant, I want to wait and see after testing on low-power devices.
2020-10-08 04:13:47 -07:00
|
|
|
&-exit-active {
|
|
|
|
opacity: 0;
|
|
|
|
transition: opacity 0.2s;
|
|
|
|
}
|
|
|
|
`}
|
|
|
|
timeout={200}
|
|
|
|
>
|
|
|
|
<FullScreenCenter zIndex={layer.zone.depth}>
|
|
|
|
{layer.canvasMovieLibraryUrl ? (
|
|
|
|
<OutfitMovieLayer
|
|
|
|
libraryUrl={layer.canvasMovieLibraryUrl}
|
|
|
|
width={canvasSize}
|
|
|
|
height={canvasSize}
|
|
|
|
isPaused={isPaused}
|
|
|
|
/>
|
|
|
|
) : (
|
|
|
|
<img
|
|
|
|
src={getBestImageUrlForLayer(layer)}
|
|
|
|
alt=""
|
|
|
|
// We manage the fade-in and fade-out separately! The fade-in
|
|
|
|
// happens here, when the <Image> finishes preloading and
|
|
|
|
// applies the src to the underlying <img>.
|
|
|
|
className={cx(
|
|
|
|
css`
|
|
|
|
object-fit: contain;
|
|
|
|
max-width: 100%;
|
|
|
|
max-height: 100%;
|
2020-09-21 18:50:27 -07:00
|
|
|
|
simplify canvas code, just use separate elements
Previously I tried to be clever and pre-optimize by putting all the layers onto one canvas… I think this probably helped by batching their paints, but it made fades less smooth by not taking advantage of native CSS transitions, and it made us dip into JS way more often than necessary.
Here, I take the simpler approach: just layers of <img> and <canvas> tags, with each animated layer on its own canvas, and letting the browser handle transitions and compositing, and separate `setInterval` timers to manage their framerates.
I have a suspicion that batching the paints could help performance more, but honestly, maybe that batching is already happening somehow, because things look pretty great on my big-screen stress test now; and so if it _is_ relevant, I want to wait and see after testing on low-power devices.
2020-10-08 04:13:47 -07:00
|
|
|
&.do-animations {
|
|
|
|
animation: fade-in 0.2s;
|
|
|
|
}
|
2020-09-21 18:50:27 -07:00
|
|
|
|
simplify canvas code, just use separate elements
Previously I tried to be clever and pre-optimize by putting all the layers onto one canvas… I think this probably helped by batching their paints, but it made fades less smooth by not taking advantage of native CSS transitions, and it made us dip into JS way more often than necessary.
Here, I take the simpler approach: just layers of <img> and <canvas> tags, with each animated layer on its own canvas, and letting the browser handle transitions and compositing, and separate `setInterval` timers to manage their framerates.
I have a suspicion that batching the paints could help performance more, but honestly, maybe that batching is already happening somehow, because things look pretty great on my big-screen stress test now; and so if it _is_ relevant, I want to wait and see after testing on low-power devices.
2020-10-08 04:13:47 -07:00
|
|
|
@keyframes fade-in {
|
|
|
|
from {
|
|
|
|
opacity: 0;
|
2020-09-21 18:50:27 -07:00
|
|
|
}
|
simplify canvas code, just use separate elements
Previously I tried to be clever and pre-optimize by putting all the layers onto one canvas… I think this probably helped by batching their paints, but it made fades less smooth by not taking advantage of native CSS transitions, and it made us dip into JS way more often than necessary.
Here, I take the simpler approach: just layers of <img> and <canvas> tags, with each animated layer on its own canvas, and letting the browser handle transitions and compositing, and separate `setInterval` timers to manage their framerates.
I have a suspicion that batching the paints could help performance more, but honestly, maybe that batching is already happening somehow, because things look pretty great on my big-screen stress test now; and so if it _is_ relevant, I want to wait and see after testing on low-power devices.
2020-10-08 04:13:47 -07:00
|
|
|
to {
|
|
|
|
opacity: 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
doTransitions && "do-animations"
|
|
|
|
)}
|
|
|
|
// This sets up the cache to not need to reload images during
|
|
|
|
// download!
|
|
|
|
// TODO: Re-enable this once we get our change into Chakra
|
|
|
|
// main. For now, this will make Downloads a bit slower, which
|
|
|
|
// is fine!
|
|
|
|
// crossOrigin="Anonymous"
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
</FullScreenCenter>
|
|
|
|
</CSSTransition>
|
|
|
|
))}
|
|
|
|
</TransitionGroup>
|
2020-08-31 18:57:29 -07:00
|
|
|
<FullScreenCenter
|
|
|
|
zIndex="9000"
|
2020-06-05 23:56:42 -07:00
|
|
|
// This is similar to our Delay util component, but Delay disappears
|
2020-07-22 22:07:45 -07:00
|
|
|
// immediately on load, whereas we want this to fade out smoothly. We
|
2020-09-13 04:12:14 -07:00
|
|
|
// also use a timeout to delay the fade-in by 0.5s, but don't delay the
|
|
|
|
// fade-out at all. (The timeout was an awkward choice, it was hard to
|
|
|
|
// find a good CSS way to specify this delay well!)
|
2020-09-22 01:44:24 -07:00
|
|
|
opacity={loadingAnything && loadingDelayHasPassed ? 1 : 0}
|
2020-09-13 04:12:14 -07:00
|
|
|
transition="opacity 0.2s"
|
2020-06-05 23:56:42 -07:00
|
|
|
>
|
2020-09-13 04:12:14 -07:00
|
|
|
{spinnerVariant === "overlay" && (
|
|
|
|
<>
|
|
|
|
<Box
|
|
|
|
position="absolute"
|
|
|
|
top="0"
|
|
|
|
left="0"
|
|
|
|
right="0"
|
|
|
|
bottom="0"
|
|
|
|
backgroundColor="gray.900"
|
|
|
|
opacity="0.7"
|
|
|
|
/>
|
|
|
|
{/* Against the dark overlay, use the Dark Mode spinner. */}
|
|
|
|
<DarkMode>
|
|
|
|
<HangerSpinner />
|
|
|
|
</DarkMode>
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
{spinnerVariant === "corner" && (
|
|
|
|
<HangerSpinner size="sm" position="absolute" bottom="2" right="2" />
|
|
|
|
)}
|
2020-08-31 18:57:29 -07:00
|
|
|
</FullScreenCenter>
|
2020-05-02 13:40:37 -07:00
|
|
|
</Box>
|
2020-04-23 15:19:36 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-08-31 18:57:29 -07:00
|
|
|
export function FullScreenCenter({ children, ...otherProps }) {
|
2020-04-23 13:31:39 -07:00
|
|
|
return (
|
|
|
|
<Flex
|
2020-04-23 15:19:36 -07:00
|
|
|
pos="absolute"
|
|
|
|
top="0"
|
|
|
|
right="0"
|
|
|
|
bottom="0"
|
|
|
|
left="0"
|
2020-04-23 13:31:39 -07:00
|
|
|
alignItems="center"
|
|
|
|
justifyContent="center"
|
2020-08-31 18:57:29 -07:00
|
|
|
{...otherProps}
|
2020-04-23 13:31:39 -07:00
|
|
|
>
|
|
|
|
{children}
|
|
|
|
</Flex>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-05-11 21:19:34 -07:00
|
|
|
function getBestImageUrlForLayer(layer) {
|
|
|
|
if (layer.svgUrl) {
|
simplify canvas code, just use separate elements
Previously I tried to be clever and pre-optimize by putting all the layers onto one canvas… I think this probably helped by batching their paints, but it made fades less smooth by not taking advantage of native CSS transitions, and it made us dip into JS way more often than necessary.
Here, I take the simpler approach: just layers of <img> and <canvas> tags, with each animated layer on its own canvas, and letting the browser handle transitions and compositing, and separate `setInterval` timers to manage their framerates.
I have a suspicion that batching the paints could help performance more, but honestly, maybe that batching is already happening somehow, because things look pretty great on my big-screen stress test now; and so if it _is_ relevant, I want to wait and see after testing on low-power devices.
2020-10-08 04:13:47 -07:00
|
|
|
return safeImageUrl(layer.svgUrl);
|
2020-05-11 21:19:34 -07:00
|
|
|
} else {
|
|
|
|
return layer.imageUrl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-05 23:56:42 -07:00
|
|
|
/**
|
|
|
|
* usePreloadLayers preloads the images for the given layers, and yields them
|
|
|
|
* when done. This enables us to keep the old outfit preview on screen until
|
|
|
|
* all the new layers are ready, then show them all at once!
|
|
|
|
*/
|
2020-07-22 21:29:57 -07:00
|
|
|
export function usePreloadLayers(layers) {
|
2020-06-05 23:56:42 -07:00
|
|
|
const [error, setError] = React.useState(null);
|
|
|
|
const [loadedLayers, setLoadedLayers] = React.useState([]);
|
simplify canvas code, just use separate elements
Previously I tried to be clever and pre-optimize by putting all the layers onto one canvas… I think this probably helped by batching their paints, but it made fades less smooth by not taking advantage of native CSS transitions, and it made us dip into JS way more often than necessary.
Here, I take the simpler approach: just layers of <img> and <canvas> tags, with each animated layer on its own canvas, and letting the browser handle transitions and compositing, and separate `setInterval` timers to manage their framerates.
I have a suspicion that batching the paints could help performance more, but honestly, maybe that batching is already happening somehow, because things look pretty great on my big-screen stress test now; and so if it _is_ relevant, I want to wait and see after testing on low-power devices.
2020-10-08 04:13:47 -07:00
|
|
|
const [layersHaveAnimations, setLayersHaveAnimations] = React.useState(false);
|
2020-06-05 23:56:42 -07:00
|
|
|
|
2020-08-31 18:41:57 -07:00
|
|
|
// NOTE: This condition would need to change if we started loading one at a
|
|
|
|
// time, or if the error case would need to show a partial state!
|
|
|
|
const loading = loadedLayers !== layers;
|
|
|
|
|
2020-06-05 23:56:42 -07:00
|
|
|
React.useEffect(() => {
|
2020-08-31 18:41:57 -07:00
|
|
|
// HACK: Don't clear the preview when we have zero layers, because it
|
|
|
|
// usually means the parent is still loading data. I feel like this isn't
|
|
|
|
// the right abstraction, though...
|
|
|
|
if (loadedLayers.length > 0 && layers.length === 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the layers already match, we can ignore extra effect triggers.
|
|
|
|
if (!loading) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-06-05 23:56:42 -07:00
|
|
|
let canceled = false;
|
|
|
|
setError(null);
|
|
|
|
|
2020-09-24 06:25:52 -07:00
|
|
|
const loadAssets = async () => {
|
|
|
|
const assetPromises = layers.map((layer) => {
|
|
|
|
if (layer.canvasMovieLibraryUrl) {
|
simplify canvas code, just use separate elements
Previously I tried to be clever and pre-optimize by putting all the layers onto one canvas… I think this probably helped by batching their paints, but it made fades less smooth by not taking advantage of native CSS transitions, and it made us dip into JS way more often than necessary.
Here, I take the simpler approach: just layers of <img> and <canvas> tags, with each animated layer on its own canvas, and letting the browser handle transitions and compositing, and separate `setInterval` timers to manage their framerates.
I have a suspicion that batching the paints could help performance more, but honestly, maybe that batching is already happening somehow, because things look pretty great on my big-screen stress test now; and so if it _is_ relevant, I want to wait and see after testing on low-power devices.
2020-10-08 04:13:47 -07:00
|
|
|
return loadMovieLibrary(layer.canvasMovieLibraryUrl).then(
|
|
|
|
(library) => ({
|
|
|
|
type: "movie",
|
|
|
|
library,
|
|
|
|
libraryUrl: layer.canvasMovieLibraryUrl,
|
|
|
|
})
|
|
|
|
);
|
2020-09-24 06:25:52 -07:00
|
|
|
} else {
|
simplify canvas code, just use separate elements
Previously I tried to be clever and pre-optimize by putting all the layers onto one canvas… I think this probably helped by batching their paints, but it made fades less smooth by not taking advantage of native CSS transitions, and it made us dip into JS way more often than necessary.
Here, I take the simpler approach: just layers of <img> and <canvas> tags, with each animated layer on its own canvas, and letting the browser handle transitions and compositing, and separate `setInterval` timers to manage their framerates.
I have a suspicion that batching the paints could help performance more, but honestly, maybe that batching is already happening somehow, because things look pretty great on my big-screen stress test now; and so if it _is_ relevant, I want to wait and see after testing on low-power devices.
2020-10-08 04:13:47 -07:00
|
|
|
return loadImage(getBestImageUrlForLayer(layer)).then((image) => ({
|
|
|
|
type: "image",
|
|
|
|
image,
|
|
|
|
}));
|
2020-09-24 06:25:52 -07:00
|
|
|
}
|
|
|
|
});
|
simplify canvas code, just use separate elements
Previously I tried to be clever and pre-optimize by putting all the layers onto one canvas… I think this probably helped by batching their paints, but it made fades less smooth by not taking advantage of native CSS transitions, and it made us dip into JS way more often than necessary.
Here, I take the simpler approach: just layers of <img> and <canvas> tags, with each animated layer on its own canvas, and letting the browser handle transitions and compositing, and separate `setInterval` timers to manage their framerates.
I have a suspicion that batching the paints could help performance more, but honestly, maybe that batching is already happening somehow, because things look pretty great on my big-screen stress test now; and so if it _is_ relevant, I want to wait and see after testing on low-power devices.
2020-10-08 04:13:47 -07:00
|
|
|
|
|
|
|
let assets;
|
2020-06-05 23:56:42 -07:00
|
|
|
try {
|
simplify canvas code, just use separate elements
Previously I tried to be clever and pre-optimize by putting all the layers onto one canvas… I think this probably helped by batching their paints, but it made fades less smooth by not taking advantage of native CSS transitions, and it made us dip into JS way more often than necessary.
Here, I take the simpler approach: just layers of <img> and <canvas> tags, with each animated layer on its own canvas, and letting the browser handle transitions and compositing, and separate `setInterval` timers to manage their framerates.
I have a suspicion that batching the paints could help performance more, but honestly, maybe that batching is already happening somehow, because things look pretty great on my big-screen stress test now; and so if it _is_ relevant, I want to wait and see after testing on low-power devices.
2020-10-08 04:13:47 -07:00
|
|
|
assets = await Promise.all(assetPromises);
|
2020-06-05 23:56:42 -07:00
|
|
|
} catch (e) {
|
|
|
|
if (canceled) return;
|
|
|
|
console.error("Error preloading outfit layers", e);
|
2020-09-24 06:25:52 -07:00
|
|
|
assetPromises.forEach((p) => p.cancel());
|
2020-06-05 23:56:42 -07:00
|
|
|
setError(e);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (canceled) return;
|
simplify canvas code, just use separate elements
Previously I tried to be clever and pre-optimize by putting all the layers onto one canvas… I think this probably helped by batching their paints, but it made fades less smooth by not taking advantage of native CSS transitions, and it made us dip into JS way more often than necessary.
Here, I take the simpler approach: just layers of <img> and <canvas> tags, with each animated layer on its own canvas, and letting the browser handle transitions and compositing, and separate `setInterval` timers to manage their framerates.
I have a suspicion that batching the paints could help performance more, but honestly, maybe that batching is already happening somehow, because things look pretty great on my big-screen stress test now; and so if it _is_ relevant, I want to wait and see after testing on low-power devices.
2020-10-08 04:13:47 -07:00
|
|
|
|
|
|
|
const newLayersHaveAnimations = assets.some(
|
|
|
|
(a) =>
|
|
|
|
a.type === "movie" &&
|
|
|
|
hasAnimations(buildMovieClip(a.library, a.libraryUrl))
|
|
|
|
);
|
2020-06-05 23:56:42 -07:00
|
|
|
setLoadedLayers(layers);
|
simplify canvas code, just use separate elements
Previously I tried to be clever and pre-optimize by putting all the layers onto one canvas… I think this probably helped by batching their paints, but it made fades less smooth by not taking advantage of native CSS transitions, and it made us dip into JS way more often than necessary.
Here, I take the simpler approach: just layers of <img> and <canvas> tags, with each animated layer on its own canvas, and letting the browser handle transitions and compositing, and separate `setInterval` timers to manage their framerates.
I have a suspicion that batching the paints could help performance more, but honestly, maybe that batching is already happening somehow, because things look pretty great on my big-screen stress test now; and so if it _is_ relevant, I want to wait and see after testing on low-power devices.
2020-10-08 04:13:47 -07:00
|
|
|
setLayersHaveAnimations(newLayersHaveAnimations);
|
2020-06-05 23:56:42 -07:00
|
|
|
};
|
|
|
|
|
2020-09-24 06:25:52 -07:00
|
|
|
loadAssets();
|
2020-06-05 23:56:42 -07:00
|
|
|
|
|
|
|
return () => {
|
|
|
|
canceled = true;
|
|
|
|
};
|
2020-08-31 19:31:37 -07:00
|
|
|
}, [layers, loadedLayers.length, loading]);
|
2020-06-05 23:56:42 -07:00
|
|
|
|
simplify canvas code, just use separate elements
Previously I tried to be clever and pre-optimize by putting all the layers onto one canvas… I think this probably helped by batching their paints, but it made fades less smooth by not taking advantage of native CSS transitions, and it made us dip into JS way more often than necessary.
Here, I take the simpler approach: just layers of <img> and <canvas> tags, with each animated layer on its own canvas, and letting the browser handle transitions and compositing, and separate `setInterval` timers to manage their framerates.
I have a suspicion that batching the paints could help performance more, but honestly, maybe that batching is already happening somehow, because things look pretty great on my big-screen stress test now; and so if it _is_ relevant, I want to wait and see after testing on low-power devices.
2020-10-08 04:13:47 -07:00
|
|
|
return { loading, error, loadedLayers, layersHaveAnimations };
|
2020-06-05 23:56:42 -07:00
|
|
|
}
|
|
|
|
|
2020-04-23 13:31:39 -07:00
|
|
|
export default OutfitPreview;
|