-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
DS-1146 DS-1193 | Looping video page heroes and inline Story Video component; reusable Caption, MediaWrapper and various Video components #389
Conversation
✅ Deploy Preview for giving-campaign ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
…unused StoryHero component
* dev: NOJIRA | Remove currently unused and POC components (#390)
…e the first frame of the video and should have the same default focus as the video itself; update getProcessedImage to resize image to 2000px wide by default if image is larger than 2000px and if crop dimension is not provided
…o due to browser glitch
@@ -16,7 +19,7 @@ import { getMaskedAsset } from './getMaskedAsset'; | |||
|
|||
export const getProcessedImage = ( | |||
imageSrc: string = '', | |||
crop: string = '', | |||
crop: string = getSbImageSize(imageSrc)?.width > 2000 ? '2000x0' : '', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Made a small change to getProcessedImage utility, so that If a crop dimension is not provided and if the image width is greater than 2000px, the crop dimension will be set to "2000x0" to keep the original aspect ratio and downsize the image to 2000px wide. I don't think there is a reason to have any images on the site that is larger than 2000px wide at the moment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Smart.
import { VideoHTMLAttributes, forwardRef } from 'react'; | ||
import { getMaskedAsset } from '@/utilities/getMaskedAsset'; | ||
import { getProcessedImage } from '@/utilities/getProcessedImage'; | ||
|
||
/** | ||
* Simple Video component that allows for webm and mp4 sources and a poster image. | ||
*/ | ||
|
||
export type VideoProps = VideoHTMLAttributes<HTMLVideoElement> & { | ||
mp4Src?: string; | ||
webmSrc?: string; | ||
posterSrc?: string; | ||
} | ||
|
||
export const Video = forwardRef<HTMLVideoElement, VideoProps>(({ | ||
mp4Src, | ||
webmSrc, | ||
posterSrc, | ||
children, | ||
className, | ||
...props | ||
}, ref) => ( | ||
<video | ||
ref={ref} | ||
poster={getProcessedImage(posterSrc)} | ||
className={className} | ||
{...props} | ||
> | ||
{webmSrc && <source src={getMaskedAsset(webmSrc)} type="video/webm" />} | ||
{mp4Src && <source src={getMaskedAsset(mp4Src)} type="video/mp4" />} | ||
{children} | ||
</video> | ||
)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generic html video component that supports a mp4 and webm sources (plus any additional via children
prop), and a preview image poster
attribute
export const MutedVideoLoop = forwardRef<HTMLVideoElement, VideoProps>((props, ref) => ( | ||
<Video ref={ref} role="presentation" muted autoPlay loop playsInline {...props} /> | ||
)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A convenience component for muted loops so I don't have to write out those same attributes all the time.
aspectRatioClass?: string; | ||
}; | ||
|
||
export const StoryVideo = ({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a counterpart of the StoryImage
component - comes with a wrapper that provides aspect ratio, width and spacing options, plus caption and other options.
export const Caption = ({ | ||
caption, | ||
isCaptionInset, | ||
captionBgColor = 'transparent', | ||
className, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extract the Caption component since it's used in a few places
aspectRatioClass?: string; // Additional eg, responsive aspect ratio classes | ||
}; | ||
|
||
export const MediaWrapper = ({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the wrapper component that's used for StoryImage and StoryVideo that provides it with a common set of options
components/Video/StoryVideo.tsx
Outdated
/** | ||
* Pause video when it goes out of view, | ||
* resume when it comes back into view if it was not manually paused by the user. | ||
*/ | ||
useEffect(() => { | ||
const video = videoRef.current; | ||
if (!video) return; | ||
|
||
if (isVideoInView && !isUserPaused) { | ||
video.play().catch(() => {}); | ||
} else { | ||
video.pause(); | ||
} | ||
}, [isVideoInView, isUserPaused]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pause the video when 90% or more is out of the viewport. Resumes playing when it comes back into view, unless user had previously manually paused the video.
…ve behavior but if used in newer version of Framer Motion this matters) and update wrong doc
@@ -45,7 +45,7 @@ export const AnimateInView = ({ | |||
duration, | |||
ease: 'easeOut', | |||
}} | |||
initial="hidden" | |||
initial={beforeAnimationState} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated AnimateInView wrapper. This still works as intended on live, but when this component is used with a newer version of Framer Motion, it doesn't work correctly. This get rid of the conflict and should be this way from the beginning - I thought this looked sus at one point and always meant to come back to do it properly even though it was still working.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason I found the bug was that Rebecca is using my AnimateInView component on Centennial which is on React 19/Next 15 and the latest version of Framer Motion (now called just Motion). She was wondering why the reduced motion setting didn't work for her site, but still works for Momentum. I came back to look at this and found that it's fixed if I make this change. @sherakama
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool. Good future gotcha prevention.
|
||
setIsBgPlaying((prev) => { | ||
if (prev) { | ||
bgVideoRef.current?.pause(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do you need the ?
here as you've already checked if the .current
value is truthy?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! I'll update 😄
} else { | ||
bgVideoRef.current | ||
?.play() | ||
.catch(() => {}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tell me more about this catch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added this because sometimes video playback might be blocked or it can't be played for some reason, which can cause issues with DOM errors. This catches the error silently and prevents that from causing issue - I could have it display a console error or warning as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the user experience if the video playback fails and you've squashed the error? Can the user self-recover? Is there a graceful failover?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly I can certainly do more with error handling, but error should be rare. So in the interest of time, I'll come back to this later @sherakama 😂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll let you judge the time/effort to spend here but overall I am not a fan of squashing errors quietly as they tend to be very difficult to troubleshoot. I'd rather see the error logged.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can update for it to show a console warning 👍🏼
Happy path testing works (and looks) great. I added @ericbakenhus for visibility and his keen eye too. |
Yes, I remember what you suggested before about a responsive image component 😄 I still plan on doing that at some point, but going to get this out the door first. It's a busy sprint 🤠 Some final clean up of redundant props coming (get rid of those that are already in MediaWrapperProps) |
I'll have to find out more about this "happy path testing" @sherakama 👀 |
Well, not a long path on this one, but the testing was:
|
Cool 😎 Yeah I've never heard that term before so I just looked that up. |
…in the shared MediaWrapperProps; better doc for aspectRatioClass; add console warning if video playback is causing error
Thank you for the review 😄 I have tested again after the final changes. Going to cut a release for this. |
READY FOR REVIEW
Summary
SbVideo
- inline looping video component with caption and other optionsVideo
,MutedVideoLoop
,StoryVideo
andVideoButton
componentsMediaWrapper
andCaption
components which replace part of the common code inStoryImage
,StoryVideo
andEmbedMedia
componentsReview By (Date)
Criticality
Review Tasks
Setup tasks and/or behavior to test
https://stanfordwebservices.slack.com/archives/C03R765SAJC/p1739990416255369
https://stanfordwebservices.slack.com/archives/C03R765SAJC/p1740163729849879
https://deploy-preview-389--giving-campaign.netlify.app/initiatives/stanford-public-humanities
https://deploy-preview-389--giving-campaign.netlify.app/stories/team-behind-the-teams (The Annotated Image component uses StoryImage)
https://deploy-preview-389--giving-campaign.netlify.app/stories/stretching-the-canvas
Associated Issues and/or People