Images not loading fully until hovered over - javascript

On my React website, I have a vertical list of screenshots of my past projects, when the page first loads the images load, but are only a sliver until the user hovers over them.
Once hovered over the images expand to their full size and then work properly. I am looking to prevent the user from hovering over them in order for it to load properly.
The list is mapped from an array
items: [
{
image: "./images/spokanepowerstroke.jpg",
title: "Spokane Power Stroke",
link: "/powerstroke",
},
...
]
<Col md="auto">
{this.state.items.map(({ title, image, link }) => (
<motion.div className="thumbnail" variants={thumbnailVariants}>
{" "}
<motion.div
key={title}
className="frame"
whileHover="hover"
variants={frameVariants}
transition={transition}
>
<p className="projectstitle">{title}</p>
<Link to={link}>
<motion.img
src={image}
alt={image}
variants={imageVariants}
transition={transition}
/>
</Link>
</motion.div>
</motion.div>
))}
</Col>
And the hover and transition effects are controlled with framer motion.
const transition = { duration: 0.5, ease: [0.43, 0.13, 0.23, 0.96] };
const thumbnailVariants = {
initial: { scale: 0.9, opacity: 0 },
enter: { scale: 1, opacity: 1, transition },
exit: {
scale: 0.5,
opacity: 0,
transition: { duration: 1.5, ...transition },
},
};
const frameVariants = {
hover: { scale: 0.95 },
};
const imageVariants = {
hover: { scale: 1.1 },
};
const changepage = {
in: {
opacity: 1,
},
out: {
opacity: 0,
},
};
const pagetransition = {
duration: 1.5,
};
I've looked over my code and have found no reason why the images are only loading partially.
The website is viewable here Website
And the Github repo with all the code is here Github
(the projects page)
Thank you in advance for your expertise.

In your App.css code, change this:
.thumbnail img { width: 46vw; height: 100%; }
to height:auto;
% heights will stretch/distort an image usually, if you want to keep aspect ratio use auto
also maybe setting width & height on the img element in your code might help prevent sizing issues, because right now the browser doesn't know how big the image actually is, and since it's loaded in via React JS not in the HTML first served it probably can't calculate it until the hover animation forces a repaint.

Related

Framer Motion (React): How to order initial, exit and layout animations?

I am using framer-motion to animate a change in grid columns.
Here is what I want to do:
I've got nine buttons in a grid (the #db-wrapper is the grid container).
The user switches to the "maximized" view (props.minimize changes) A fourth column gets added to the grid, the buttons which are already there move to their new positions, leaving empty cells for the remaining buttons. Then, new buttons slide in from the bottom, filling the empty cells. The buttons in the grid are now positioned in consecutive cells.
Now the user switches back to the minimized view. First, all the buttons that will get deleted should slide out to the bottom.
Then, the remaining buttons should move to their new positions in the grid (their old positions, like at the beginning), so that they fill consecutive cells.
(Basically, I want to do step 1 and 2 backwards after the user has switched back)
I've already achieved step 1 and 2. Here is the functional component:
const DrumButtons = (props) => {
const drumButtonsVariants = {
hidden: {
y: "140vh",
},
visible: {
y: 0,
transition: {
type: "tween",
duration: 1,
delay: 0.1,
},
},
exit: {
y: "140vh",
transition: {
type: "tween",
duration: 1,
delay: 0.1,
},
},
};
let dbWrapperStyle = {};
if (!props.minimized) {
dbWrapperStyle = {
gridTemplateColumns: "1fr 1fr 1fr 1fr",
};
}
let singleWrapperStyle = {
width: "100%",
height: "100%",
};
let buttonTransition = { duration: 0.5, delay: 0.1 };
return (
<div id="db-wrapper" style={dbWrapperStyle}>
<AnimatePresence>
{props.buttonsarr.map((elem, i) => {
return (
<motion.div
variants={drumButtonsVariants}
initial="hidden"
animate="visible"
exit="exit"
key={elem.press}
style={singleWrapperStyle}
>
<motion.button
layout
transition={buttonTransition}
key={elem.press}
className="drum-pad"
onClick={dbHandleClickWrapper(
props.changetext,
props.buttonsarr
)}
id={elem.name}
>
<span className="front">{elem.press.toUpperCase()}</span>
<audio
key={elem.press.toUpperCase()}
id={elem.press.toUpperCase()}
src={props.buttonsarr[i].source}
preload="auto"
className="clip"
></audio>
</motion.button>
</motion.div>
);
})}
</AnimatePresence>
</div>
);
};
What is the problem, exactly?
So here's what currently happens when switching back from "maximized" to "minimized":
The buttons that are no longer needed slide out to the bottom. At the same time, the 4 grid columns get reduced to 3 columns. So the remaining buttons slide to their position in the 3-column-wide grid, leaving empty cells for the buttons that are currently sliding out, but still present in the DOM.
After the other buttons have been removed, the remaining buttons move again to fill consecutive cells of the grid.
Here is what I've tried:
I've tried adding a when: "beforeChildren" to the transitions of the drumButtonsVariants. Nothing changed.
I've played around with the delays, but I've never achieved the desired outcome. If I delay the layout animation, the removal of the buttons from the DOM will get delayed too, resulting in the same unwanted outcome.
You can view my full code here:
(main branch) https://github.com/Julian-Sz/FCC-Drum-Machine/tree/main
(version with the problem) https://github.com/Julian-Sz/FCC-Drum-Machine/tree/284606cac7cbc4bc6e13bf432c563eab4814d370
Feel free to copy and fork this repository!
The trick is to use State for the dbWrapperStyle.
Then, AnimatePresence with onExitComplete can be used.
This happens when the user switches to minimized again:
The removed buttons slide out (but they are still in the DOM - occupying grid cells)
Now they get removed from the DOM, and onExitComplete fires: The grid columns change to 3 columns and the component gets rerendered. The removal from the DOM and the grid change are happening after each other (without any delay) - resulting in a smooth animation!
Here is the new component:
const DrumButtons = (props) => {
const drumButtonsVariants = {
visible: {
y: 0,
transition: {
type: "tween",
duration: 0.8,
delay: 0.1,
},
},
exit: {
y: "140vh",
transition: {
type: "tween",
duration: 0.8,
delay: 0.1,
},
},
};
// BEFORE------------
// let dbWrapperStyle = {};
// if (!props.minimized) {
// dbWrapperStyle = {
// gridTemplateColumns: "1fr 1fr 1fr 1fr",
// };
// }
// AFTER-------------
const [dbWrapperStyle, setWrapperStyle] = useState({});
useEffect(() => {
if (!props.minimized) {
setWrapperStyle({ gridTemplateColumns: "1fr 1fr 1fr 1fr" });
}
}, [props.minimized]);
let singleWrapperStyle = {
width: "100%",
height: "100%",
};
let buttonTransition = {
type: "spring",
duration: 0.9,
delay: 0.1,
bounce: 0.5,
};
return (
<div id="db-wrapper" style={dbWrapperStyle}>
<AnimatePresence
onExitComplete={() => {
setWrapperStyle({ gridTemplateColumns: "1fr 1fr 1fr" });
}}
>
{props.buttonsarr.map((elem, i) => {
return (
<motion.div
variants={drumButtonsVariants}
initial="exit"
animate="visible"
exit="exit"
key={elem.press}
style={singleWrapperStyle}
>
<motion.button
layout
transition={buttonTransition}
key={elem.press}
className="drum-pad"
onClick={dbHandleClickWrapper(
props.changetext,
props.buttonsarr
)}
id={elem.name}
>
<span className="front">{elem.press.toUpperCase()}</span>
<audio
key={elem.press.toUpperCase()}
id={elem.press.toUpperCase()}
src={props.buttonsarr[i].source}
preload="auto"
className="clip"
></audio>
</motion.button>
</motion.div>
);
})}
</AnimatePresence>
</div>
);
};

Framer motion animate when element is in-view (When you scroll to element)

Is there any built-in way to make the animation start when the element is in-view (for example, when we scroll to the element)?
Framer Motion has mount animations section which says:
When a component mounts, it'll automatically animate to the values in animate if they're different from those defined in style or initial
So I couldn't really find a straight forward way to make the animation start when it comes into view.
However, I reached the only option I see for now is using Animation Controls which means I'll have to implement a listener on the scroll manually and trigger the control.start(), any easier ways are much appreciated.
framer-motion has built-in support for this use case since version 5.3.
Here's a CodeSandbox demonstrating the pattern: https://codesandbox.io/s/framer-motion-animate-in-view-5-3-94j13
Relevant code:
function FadeInWhenVisible({ children }) {
return (
<motion.div
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
transition={{ duration: 0.3 }}
variants={{
visible: { opacity: 1, scale: 1 },
hidden: { opacity: 0, scale: 0 }
}}
>
{children}
</motion.div>
);
}
Usage:
<FadeInWhenVisible>
<Box />
</FadeInWhenVisible>
Previous versions:
You can currently use the imperative animation controls to achieve this effect. Intersection observers are useful to detect if an element is currently visible.
Here's a CodeSandbox demonstrating the pattern: https://codesandbox.io/s/framer-motion-animate-in-view-gqcc8.
Relevant code:
function FadeInWhenVisible({ children }) {
const controls = useAnimation();
const [ref, inView] = useInView();
useEffect(() => {
if (inView) {
controls.start("visible");
}
}, [controls, inView]);
return (
<motion.div
ref={ref}
animate={controls}
initial="hidden"
transition={{ duration: 0.3 }}
variants={{
visible: { opacity: 1, scale: 1 },
hidden: { opacity: 0, scale: 0 }
}}
>
{children}
</motion.div>
);
}

How to connect two animations on one element with React Spring?

I want to create a button that will trigger every 2 seconds and change it's scale from 1 to 1.5 and have on the same element second animation but only when you put mouse over it. My problem is that I don't know how to connect this two useSpring hooks.
My animation on hover:
const buttonAnimation = useSpring({
backgroundColor: !isVisible ? '#4b1220' : '#fff',
});
and I want to apply second animation
const buttonInfAnimation = useSpring({
from: {
scale: 1,
},
to: {
scale: 1.5,
},
});
When I call this like this, I'm getting only latest animation in code:
<animated.button
style={{...buttonInfAnimation, ...buttonAnimation}}
onMouseLeave={() => setVisibility(false)}
onMouseEnter={() => setVisibility(true)}
className="hover"
></animated.button>
How to apply two animations with React Springs on one element? I have tried to implement useSprings but I don't understand it correctly how to use this.
https://www.react-spring.io/docs/hooks/use-springs
You do not have to create two separate spring animation. You can combine them. And for the scale you have to use the transform css property.
const buttonInfAnimation = useSpring({
from: {
transform: 'scale(1)',
},
to: {
transform: 'scale(1.5)',
backgroundColor: !isVisible ? '#4b1220' : '#fff',
},
});

The background color is not rendering correctly on an animation I am trying to create on with the package react-moves

I'm using the react package react-moves to create an animation of three separate stripes that will appear on a page when the page refreshes. The background color doesn't seem to be rendering correctly. My react component is entered below;
class Stripes extends Component {
state = {
stripes: [
{
background:'#98c5e9',
left: 120,
rotate: 25,
top: -260 ,
delay: 0
},
{
background:'#ffffff',
left: 360,
rotate: 25,
top: -397,
delay: 500
},
{
background:'#98c5e9',
left: 600,
rotate: 25,
top: -498,
delay: 1000
}
]
}
AnimatedStripesHander = () => (
this.state.stripes.map((stripe, index) => (
<Animate
key={index}
show={true}
start={{
background: '#ffffff',
opacity: 0,
left: 0,
rotate: 0,
top: 0
}}
enter={{
background: [stripe.background],
opacity: [1],
left: [stripe.left],
rotate: [stripe.rotate],
top: [stripe.top],
timing: { delay: stripe.delay, duration: 500, ease: easePolyOut },
events: {
end() {
console.log(stripe.background)
}
}
}}
>
{({opacity,left,rotate,top,background})=>{
return(
<div
style={{
background,
opacity,
transform: `rotate(${rotate}deg) translate(${left}px,${top}px)`
}}
></div>
);
}}
</Animate>
))
)
The issue I am having trouble with is the background tag in the style object returned at the bottom. This background is erroring but I just can't seem to understand why. When I remove it everything else in the animation works very good however not the background color, has anybody got any experience with react-moves?
Thanks
There's no npm package react-moves so I'm assuming that you're using react-move.
The docs for react-move say that you need to use a third-party package to use color interpolation:
https://github.com/react-tools/react-move#cadillac-interpolation----depends-on-d3-interpolate

Animating the border radius in React Native

I'm unable to animate the borderRadius property in a ReactNative Image, it seems to only re-render the image as the animation completes. It fades out on animation start, and fades back in on animation completion. This only happens on Android; on iOS the animation plays properly.
I am trying to animate a circle expanding into a square by animating the borderRadius:
constructor(props) {
super(props);
this.state = {
borderRadius: new Animated.Value(ALBUM_CIRCLE_DIAMETER /2)
};
}
_zoomIn = () => {
Animated.timing(
this.state.borderRadius,
{
toValue: 0,
duration: ZOOM_ANIMATION_DURATION_MS,
easing: Easing.linear
}
).start()
}
And the markup:
<Animated.Image
style={[
styles.albumArtCircle,
{ width: this.state.albumArtWidth },
{ height: this.state.albumArtHeight },
{ borderRadius: this.state.borderRadius },
]}
resizeMode='contain'
source={require('../images/sampleAlbum.jpg')}>
</Animated.Image>
Right! Remove resizeMode property. This will solve your problem
Removing resizeMode='contain' made this work.

Categories