I am trying to make an animation that scrolls from left to right the child elements of a div, I have looked around but none of the other questions with answers seem to work, so far this is what I have accomplished:
Home.css:
#keyframes scroll {
20% {
transform: translateX(-100vw);
}
40% {
transform: translateX(-200vw);
}
60% {
transform: translateX(-300vw);
}
80% {
transform: translateX(-400vw);
}
100% {
transform: translateX(0vw);
}
}
.section {
display: flex;
flex-direction: row;
font-family: Arial, sans-serif;
font-weight: bold;
transition: 1s;
width: 100vw;
height: 100vh;
animation-name: scroll;
animation-duration: 1s;
}
.child1 {
background-color: #c0392b;
flex: none;
width: 100vw;
height: 100vh;
}
.child2 {
background-color: #e67e22;
flex: none;
width: 100vw;
height: 100vh;
}
.child3 {
background-color: #27ae60;
flex: none;
width: 100vw;
height: 100vh;
}
.child4 {
background-color: #2980b9;
flex: none;
width: 100vw;
height: 100vh;
}
.child5 {
background-color: #8e44ad;
flex: none;
width: 100vw;
height: 100vh;
}
Home.jsx:
import { forwardRef, useEffect, useRef, useState } from "react";
import { Link } from "react-router-dom";
import './Home.css';
const Child1 = forwardRef((props, ref) => {
return <div className="child1">
<h1>Child1</h1>
</div>;
});
const Child2 = forwardRef((props, ref) => {
return <div className="child2">
<h1>Child2</h1>
</div>;
});
const Child3 = forwardRef((props, ref) => {
return <div className="child3">
<h1>Child3</h1>
</div>;
});
const Child4 = forwardRef((props, ref) => {
return <div className="child4">
<h1>Child4</h1>
</div>;
});
const Child5 = forwardRef((props, ref) => {
return <div className="child5">
<h1>Child5</h1>
</div>;
});
function Home() {
let section = useRef();
let [currentSection, setCurrentSection] = useState(0);
useEffect(() => {
window.addEventListener("keypress", (event) => {
if(event.key === "d") {
// scroll right (play animation forwards by one step)
} else if(event.key === "a") {
// scroll left (play animation backwards by one step)
}
});
});
return <div ref={section} className="section">
<Child1></Child1>
<Child2></Child2>
<Child3></Child3>
<Child4></Child4>
<Child5></Child5>
</div>
}
export default Home;
The problem is that the animation plays all at the beginning and I cannot figure out a way to play it only when the keyboard event is triggered, if the event.key is an "a" then the elements should scroll to the left otherwise if the event.key is a "d" then the elements should scroll to the right.
Here is the link to the CodeSandbox.
As I understood you want to make parent scrollable only with pressing keys "a" and "d". I think I have found a solution which would work for you.
My solution:
Removing keyframes and separating it into 5 different classes. So here is CSS file:
.section1 {
transform: translateX(0);
}
.section2 {
transform: translateX(-100vw);
}
.section3 {
transform: translateX(-200vw);
}
.section4 {
transform: translateX(-300vw);
}
.section5 {
transform: translateX(-400vw);
}
.section {
display: flex;
flex-direction: row;
font-family: Arial, sans-serif;
font-weight: bold;
transition: 1s;
width: 100vw;
height: 100vh;
animation-name: scroll;
animation-duration: 1s;
}
.child1 {
background-color: #c0392b;
flex: none;
width: 100vw;
height: 100vh;
}
.child2 {
background-color: #e67e22;
flex: none;
width: 100vw;
height: 100vh;
}
.child3 {
background-color: #27ae60;
flex: none;
width: 100vw;
height: 100vh;
}
.child4 {
background-color: #2980b9;
flex: none;
width: 100vw;
height: 100vh;
}
.child5 {
background-color: #8e44ad;
flex: none;
width: 100vw;
height: 100vh;
}
Now I added some JS behaviour to appropriately 'scroll' left or right. I divided moving left or right into functions, and adding numberOfSections as constant. Here is JS file:
import { forwardRef, useCallback, useEffect, useRef, useState } from "react";
import { Link } from "react-router-dom";
import "./Home.css";
const Child1 = forwardRef((props, ref) => {
return (
<div className="child1">
<h1>Child1</h1>
</div>
);
});
const Child2 = forwardRef((props, ref) => {
return (
<div className="child2">
<h1>Child2</h1>
</div>
);
});
const Child3 = forwardRef((props, ref) => {
return (
<div className="child3">
<h1>Child3</h1>
</div>
);
});
const Child4 = forwardRef((props, ref) => {
return (
<div className="child4">
<h1>Child4</h1>
</div>
);
});
const Child5 = forwardRef((props, ref) => {
return (
<div className="child5">
<h1>Child5</h1>
</div>
);
});
const numberOfSections = 5;
function Home() {
const section = useRef();
const [currentSection, setCurrentSection] = useState(1);
const moveLeft = () => {
section.current.classList.remove(`section${currentSection}`);
section.current.classList.add(`section${currentSection - 1}`);
setCurrentSection((prevSection) => prevSection - 1);
};
const moveRight = () => {
section.current.classList.remove(`section${currentSection}`);
section.current.classList.add(`section${currentSection + 1}`);
setCurrentSection((prevSection) => prevSection + 1);
};
const changeSection = (event) => {
if (event.key === "d") {
if (currentSection < numberOfSections) {
moveRight();
}
} else if (event.key === "a") {
if (currentSection > 1) {
moveLeft();
}
}
};
useEffect(() => {
window.addEventListener("keypress", changeSection);
return () => window.removeEventListener("keypress", changeSection);
}, [currentSection]);
return (
<div ref={section} className="section">
<Child1></Child1>
<Child2></Child2>
<Child3></Child3>
<Child4></Child4>
<Child5></Child5>
</div>
);
}
export default Home;
I have tested it and it works.
By the way, my suggestions is to wrap some of these functions into useCallback if you plan to expand Home component. And I suggest to use some of npm packages compatible with React for directly manipulating CSS within JS, such as styled-components, which in this case would make it a lot easier to create Carousel, becouse now in order to create more Child components, you need to make new CSS classes.
But this above fixes the given problem. I hope I helped you :)
this solution is tightly cuppled to your example but from my perspective, you could make it more generic.
I didn't use CSS animation instead I used the scrollTo method to achieve scroll behavior.
codesandbox link
Home.jsx
import { forwardRef, useEffect, useRef, useState } from "react";
import "./styles.css";
// I not using forwardRef and ref at all
const Child1 = forwardRef((props, ref) => {
return (
<div className="child1">
<h1>Child1</h1>
</div>
);
});
const Child2 = forwardRef((props, ref) => {
return (
<div className="child2">
<h1>Child2</h1>
</div>
);
});
const Child3 = forwardRef((props, ref) => {
return (
<div className="child3">
<h1>Child3</h1>
</div>
);
});
const Child4 = forwardRef((props, ref) => {
return (
<div className="child4">
<h1>Child4</h1>
</div>
);
});
const Child5 = forwardRef((props, ref) => {
return (
<div className="child5">
<h1>Child5</h1>
</div>
);
});
function App() {
let section = useRef();
let [currentSection, setCurrentSection] = useState(0);
useEffect(() => {
const handler = (event) => {
if (event.key === "d") {
if (currentSection === 4) return;
setCurrentSection((prev) => prev + 1);
} else if (event.key === "a") {
if (currentSection === 0) return;
setCurrentSection((prev) => prev - 1);
}
};
window.addEventListener("keypress", handler);
// you should clean up you EventListener
return () => {
window.removeEventListener("keypress", handler);
};
}, [currentSection]);
useEffect(() => {
window.scrollTo({
behavior: "smooth",
left: window.innerWidth * currentSection,
top: 0,
});
}, [currentSection]);
return (
<div ref={section} className="section">
<Child1></Child1>
<Child2></Child2>
<Child3></Child3>
<Child4></Child4>
<Child5></Child5>
</div>
);
}
export default App;
Home.css
.section {
display: flex;
flex-direction: row;
font-family: Arial, sans-serif;
font-weight: bold;
transition: 1s;
width: 100vw;
height: 100vh;
}
.child1 {
background-color: #c0392b;
flex: none;
width: 100vw;
height: 100vh;
}
.child2 {
background-color: #e67e22;
flex: none;
width: 100vw;
height: 100vh;
}
.child3 {
background-color: #27ae60;
flex: none;
width: 100vw;
height: 100vh;
}
.child4 {
background-color: #2980b9;
flex: none;
width: 100vw;
height: 100vh;
}
.child5 {
background-color: #8e44ad;
flex: none;
width: 100vw;
height: 100vh;
}
Related
I am trying to build a component where you can visualize on what section are you in and allows you to easily move sections on the page.
My page is structured like this
<SectionManager /> // the absolutely positioned element
<Navbar />
<Menu />
<Section1 />
<Section2 />
<Section3 />
{...}
I want to have every even numbered section to have a black background and the others to have a white background. Now I want the text inside my SectionManager component to be white when overlapping a black background and black when overlapping a white background.
Here is a photo:
My component is the one on the left. And when you scroll down to the black section I want just the about me text and the circle after that to turn white.
Sorry if this is a stupid question by I searched for hours and did not find anything. I tried mix-blend-mode but it did not work.
Here the code for my component:
const SectionManager: React.FC = () => {
const globalState = React.useContext(MyContext);
const observerCallback = (entries: IntersectionObserverEntry[]) => {
...
};
const observerOptions = React.useMemo(
...
);
React.useEffect(() => {
const observer = new IntersectionObserver(...);
globalState.currentSections.forEach((section: HTMLElement) => {
observer.observe(section);
});
}, []);
const sections = [
{
text: "Hello!",
},
{
text: "about me",
},
{
text: "work i did",
},
{
text: "contact",
},
];
return (
<div className={styles.sectionManager}>
{sections.map((section, sectionID) => (
<>
{sectionID > 0 && (
<div className={styles.sectionManager_separator}></div>
)}
<div
className={
sectionID === globalState.activeSectionId
? `${styles.sectionManager_item} ${styles.sectionManager_itemActive}`
: styles.sectionManager_item
}
>
<p>{section.text}</p>
</div>
</>
))}
</div>
);
};
export default SectionManager;
here is the scss file:
.sectionManager {
position: fixed;
z-index: 100;
right: 30px;
top: 50%;
display: flex;
flex-direction: column;
align-items: flex-end;
transform: translateY(-50%);
&_separator {
width: 1px;
height: 25px;
background: $text-secondary-dark;
margin-right: 7px;
}
&_itemActive {
&::after {
background-color: $text-primary-light !important;
transform: scale(1) !important;
}
p {
color: $text-primary-light !important;
transform: scaleX(1) !important;
}
}
&_item {
mix-blend-mode: difference;
display: flex;
align-items: center;
font-size: 1rem;
margin-top: 5px;
cursor: pointer;
background: transparent;
#include transition();
&:hover {
&::after {
background-color: $text-primary-light;
transform: scale(1);
}
p {
transform: scaleX(1);
color: $text-primary-light;
}
}
p {
margin: 0;
transform: scaleX(0);
transform-origin: right;
color: $text-secondary-light;
#include transition();
}
&::after {
content: "";
width: 15px;
height: 15px;
border-radius: 999999px;
margin-left: 10px;
transform: scale(0.9);
background: $text-secondary-dark;
#include transition();
}
}
}
And for the section background I am not doing anything fancy, I am just setting a background-color property on there.
Thank you in advance!
Edit:
I want something similar to that. The design is in figma.
I solved the issue!
I ended up getting all the sections on my page using querySelector and using an IntersectionObserver to get the section that is in viewPort and get its background color, then passing the background color to my component using data-section-bg.
Here is the whole component code:
const SectionManager: React.FC = () => {
const [currentSectionBg, setCurrentSectionsBg] =
React.useState<string>("#fff");
const globalState = React.useContext(MyContext);
const observerCallback = (entries: IntersectionObserverEntry[]) => {
// other observer ...
};
const sectionColorObserverCallback = (
entries: IntersectionObserverEntry[]
) => {
entries.forEach((entry) => {
if (entry.intersectionRatio > 0.25) {
const sectionBgColor = (
document.getElementById(entry.target.id) as HTMLElement
).style.backgroundColor;
console.log(sectionBgColor);
setCurrentSectionsBg(sectionBgColor);
}
});
};
const observerOptions = React.useMemo(
() => ({
root: null,
rootMargin: "0px",
threshold: 0.25,
}),
[]
);
React.useEffect(() => {
const observer = new IntersectionObserver(
// other observer...
);
globalState.currentSections.forEach((section: HTMLElement) => {
// other observer...
});
// Detect Section Color Observer
const allSections = document.querySelectorAll("section");
if (!allSections) return;
const sectionColorObserver = new IntersectionObserver(
sectionColorObserverCallback,
observerOptions
);
allSections.forEach((section, sectionId) => {
section.id = `SECTION_${sectionId}`;
section.style.backgroundColor = sectionId % 2 === 0 ? "#fff" : "#000";
sectionColorObserver.observe(section);
});
}, []);
const sections = [
{
text: "Hello!",
},
{
text: "about me",
},
{
text: "work i did",
},
{
text: "contact",
},
];
return (
<motion.div
initial={{ x: 150, opacity: 0 }}
animate={{
x: 0,
y: "-50%",
opacity: 1,
transition: {
duration: 1,
delay: 0.8,
ease: defaultAnimationEasing,
},
}}
className={styles.sectionManager}
>
{sections.map((section, sectionID) => (
<>
{sectionID > 0 && (
<div className={styles.sectionManager_separator}></div>
)}
<div
data-section-bg={currentSectionBg}
className={
sectionID === globalState.activeSectionId
? `${styles.sectionManager_item} ${styles.sectionManager_itemActive}`
: styles.sectionManager_item
}
>
<p>{section.text}</p>
</div>
</>
))}
</motion.div>
);
};
export default SectionManager;
Here is what I added to my scss File:
[data-section-bg="rgb(0, 0, 0)"] {
&:hover {
&::after {
background: $text-primary-dark !important;
}
p {
color: $text-primary-dark;
}
}
p {
color: $text-primary-dark;
}
}
[data-section-bg="rgb(255, 255, 255)"] {
&:hover {
&::after {
background: $text-primary-light !important;
}
p {
color: $text-primary-light;
}
}
p {
color: $text-primary-light;
}
}
&_itemActive[data-section-bg="rgb(255, 255, 255)"] {
&::after {
background-color: $text-primary-light !important;
}
}
&_itemActive[data-section-bg="rgb(0, 0, 0)"] {
&::after {
background-color: $text-primary-dark !important;
}
}
Below is my code for a checkout page where i have the CardElement and Iam processing the payment through stripe. It was working fine 2 days back but it has disappeared from the UI all of a sudden. enter image description hereIs there any changes Stripe has made in its CardElement package or Am I missing anything in my code?
import React, { useEffect, useState } from "react";
import Header from "./Header";
import styled from "styled-components";
import { useSelector, useDispatch } from "react-redux";
import { selectUserName, selectUserEmail } from "../features/userSlice";
import {
selectCart,
selectCartTotalAmount,
removeAllItems,
} from "../features/cartSlice";
import Orders from "./Orders";
import { CardElement, useStripe, useElements } from "#stripe/react-stripe-js";
import { useHistory } from "react-router-dom";
import axios from "../axios";
import { db } from "../firebase";
function Checkout() {
const userName = useSelector(selectUserName);
const userEmail = useSelector(selectUserEmail);
const cart = useSelector(selectCart);
const cartTotalAmount = useSelector(selectCartTotalAmount);
const history = useHistory();
const [succeeded, setSucceeded] = useState(false);
const [required, setRequired] = useState("");
const [msg, setMsg] = useState("");
const [processing, setProcessing] = useState("");
const [address, setAddress] = useState("");
const [error, setError] = useState(null);
const [disabled, setDisabled] = useState(true);
const [clientSecret, setClientSecret] = useState(true);
const stripe = useStripe();
const elements = useElements();
const dispatch = useDispatch();
console.log("email", userEmail);
const handlechange = (e) => {
setAddress(e.target.value);
// else {
// setRequired(false);
// }
//localStorage.setItem("address", address);
};
// const goToPreview = () => {
// return <Preview address={address} />;
// };
const validateAddress = () => {};
useEffect(() => {
const getClientSecret = async () => {
const response = await axios({
method: "post",
url: `/payments/create?total=${cartTotalAmount * 100}`,
});
setClientSecret(response.data.clientSecret);
};
getClientSecret();
}, [cart]);
console.log("secret is", clientSecret);
const handleSubmit = async (e) => {
e.preventDefault();
if (address === "") {
setRequired(true);
setMsg("*Address is required!");
} else {
setProcessing(true);
console.log(processing);
const payload = await stripe
.confirmCardPayment(clientSecret, {
payment_method: {
card: elements.getElement(CardElement),
},
})
.then(({ paymentIntent }) => {
db.collection("users")
.doc(userEmail)
.collection("orders")
.doc(paymentIntent.id)
.set({
cart: cart,
amount: paymentIntent.amount,
created: paymentIntent.created,
address: address,
});
setSucceeded(true);
setError(null);
setProcessing(false);
dispatch(removeAllItems());
history.replace("/orders");
});
}
};
const handleCardChange = (e) => {
setDisabled(e.empty);
setError(e.error ? e.error.message : "");
};
return (
<div>
<Header />
<Container>
<Name>
<p>Name : {userName}</p>
</Name>
<Email>
<p>Email : {userEmail}</p>
</Email>
<Address>
<p>Address : </p>
<input type="text" onChange={handlechange} />
</Address>
<small>{msg}</small>
<Order>
<p>Order : </p>
</Order>
{cart.map((item) => (
<OrderCard>
<img src={item.image} />
<p>{item.title}</p>
<small>x</small>
<p>{item.cartQuantity}</p>
</OrderCard>
))}
<Payment>
Payment method :
<form>
<CardElement onChange={handleCardChange} />
</form>
</Payment>
<Total>Total : ${cartTotalAmount}</Total>
<button
onClick={handleSubmit}
disabled={processing || disabled || succeeded}
>
{processing ? "Processing" : "Place your order"}
</button>
{error && <div>{error}</div>}
</Container>
</div>
);
}
export default Checkout;
const Container = styled.div`
padding-top: 70px;
width: 100%;
height: 100vh;
background: linear-gradient(to bottom right, #feedf6, #fcf0e2);
margin: auto;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
button {
margin-top: 30px;
margin-bottom: 30px;
margin-left: 40rem;
align-items: center;
}
small {
margin-top: 5px;
margin-left: 20rem;
}
`;
const Name = styled.div`
margin-left: 35rem;
/* margin-right: 10px;
margin-left: 10px; */
`;
const Email = styled.div`
margin-left: 35rem;
/* margin-right: 10px;
margin-left: 10px; */
`;
const Address = styled.div`
display: flex;
justify-content: center;
margin-left: 35rem;
h3 {
margin-right: 10px;
margin-left: 5rem;
}
input {
margin-top: 20px;
margin-left: 5px;
width: 300px;
height: 100px;
}
`;
const Order = styled.div`
margin-left: 35rem;
`;
const OrderCard = styled.div`
display: flex;
justify-content: center;
margin-left: 35rem;
img {
width: 40px;
height: 40px;
}
small {
margin-top: 17px;
margin-left: 10px;
}
p {
margin-left: 20px;
}
`;
const Total = styled.div`
margin-left: 35rem;
`;
const Payment = styled.div`
margin-left: 35rem;
margin-bottom: 20px;
form {
margin-top: 10px;
flex: 0.8;
width: 600px;
height: 40px;
}
`;
I am trying to create something like the fullpage.js effect.
vertical slider on scrolling for a small amount
I use Intersection Observer but any solution could help
when threshold: 0.5, go to the bottom of the section, or show the whole section in view.
https://codepen.io/Safaa_alnabhan/pen/OJOQWmX
<div class="slider">
<div class="slide1"></div>
<div class="slide2"></div>
<div class="slide3"></div>
</div>
<style>
.slider {
height: 100vh;
overflow-x:hidden;
overflow-y:scroll;
-ms-overflow-style: none;
scrollbar-width: none;
}
.slider::-webkit-scrollbar {
display: none;
}
.slide1 {
height: 100vh;
background:red;
}
.slide2 {
height: 100vh;
background:green;
}
.slide3 {
height: 100vh;
background:blue;
}
</style>
<script>
const slide1 = document.querySelector('.slide1');
const slide2 = document.querySelector('.slide2');
const slide3 = document.querySelector('.slide3');
const sliders = document.querySelectorAll('.slider');
const options = {
threshold: 0.5,
}
const callback = (entries) => {
for(const entry of entries) {
if (entry.isIntersecting) {
entry.target.scrollBy(0, 300);
}
}
};
const observer = new IntersectionObserver(callback, options);
sliders.forEach(slider => observer.observe(slider));
</script>
I would like to implement the following: The animation should only start when I hover the mouse over the div. After I hovered over the div, the end number should remain visible and not change to the start value.
This is my code:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Animation</title>
<style>
.counter{
color: white;
font-size: 100px;
height: 300px;
width: 400px;
background-color: black;
display: flex;
align-items:center;
justify-content: center;
}
.animate{
position:absolute;
opacity:0;
transition:0s 180s;
}
.animate:hover {
opacity:1;
transition:0s;
}
</style>
</head>
<body>
<div id="animate" style="background-color: orange; width: 300px; height: 200px;" class="counter" data-target="500">0</div>
<script>
const counters = document.querySelectorAll('.counter');
for(let n of counters) {
const updateCount = () => {
const target = + n.getAttribute('data-target');
const count = + n.innerText;
const speed = 5000; // change animation speed here
const inc = target / speed;
if(count < target) {
n.innerText = Math.ceil(count + inc);
setTimeout(updateCount, 1);
} else {
n.innerText = target;
}
}
updateCount();
}
</script>
</body>
</html>
Add onmousover to id="animate"
<div id="animate" style="background-color: orange; width: 300px; height: 200px;" class="counter" data-target="500" onmouseover="animationEffect();">0</div>
Wrap the whole script in a method:
function animationEffect(){
const counters = document.querySelectorAll('.counter');
for(let n of counters) {
const updateCount = () => {
const target = + n.getAttribute('data-target');
const count = + n.innerText;
const speed = 5000; // change animation speed here
const inc = target / speed;
if(count < target) {
n.innerText = Math.ceil(count + inc);
setTimeout(updateCount, 1);
} else {
n.innerText = target;
}
}
updateCount();
}
}
Should solve the problem
EDIT:
The old answer was refering to the question before being edited. For the current case the following could be done:
const updateCount = n => {
const target = +n.getAttribute('data-target')
const count = +n.innerText
const speed = 5000 // change animation speed here
const inc = target / speed
if (count < target) {
n.innerText = Math.ceil(count + inc)
requestAnimationFrame(() => updateCount(n))
} else {
n.innerText = target
}
}
const counters = document.querySelectorAll('.counter')
for (let n of counters) {
n.addEventListener('mouseenter', () => updateCount(n), {
once: true
})
}
.counter {
color: white;
font-size: 100px;
height: 300px;
width: 400px;
background-color: black;
display: flex;
align-items: center;
justify-content: center;
}
.animate {
position: absolute;
opacity: 0;
transition: 0s 180s;
}
.animate:hover {
opacity: 1;
transition: 0s;
}
<div id="animate" style="background-color: orange; width: 300px; height: 200px" class="counter" data-target="500">
0
</div>
Old answer:
You would need to add a mouseenter event to the parent element. Note that the {once: true} option will make the event only fire once.
const parent = document.getElementById('parent')
parent.addEventListener('mouseenter', mouseEnterHandler, {once: true})
Then define the mouseEnterHandler callback as follows:
function mouseEnterHandler() {
for (let n of counters) {
n.style.display = 'block'
updateCount(n)
}
/* If you only have one counter then just get it by its Id:
const div = document.getElementById('hover-content')
div.style.display = 'block'
updateCount(div)
*/
}
n.style.display = 'block' will make the counter visible so no need for the css rule #parent:hover #hover-content { display:block; }.
Here is a working example:
const updateCount = n => {
const target = +n.getAttribute('data-target')
const count = +n.innerText
const speed = 5000 // change animation speed here
const inc = target / speed
if (count < target) {
n.innerText = Math.ceil(count + inc)
requestAnimationFrame(() => updateCount(n))
} else {
n.innerText = target
}
}
const counters = document.querySelectorAll('.counter')
const parent = document.getElementById('parent')
parent.addEventListener('mouseenter', mouseEnterHandler, {
once: true
})
function mouseEnterHandler() {
for (let n of counters) {
n.style.display = 'block'
updateCount(n)
}
}
.counter {
color: white;
font-size: 100px;
height: 140px;
width: 400px;
background-color: black;
display: flex;
align-items: center;
justify-content: center;
}
#hover-content {
display: none;
}
<div id="parent">
Some content
<div hidden id="hover-content" class="counter" data-target="232">0</div>
</div>
I've made a simple audio player, but now I want to add a song progress bar that fills up the timeline as the song plays, this is my html:
<div id="ap-timeline" onClick={this.mouseMove} ref={(timeline) => { this.timeline = timeline }}>
<div id="ap-handle" onMouseDown={this.mouseDown} ref={(handle) => { this.handle = handle }} />
<div id="ap-handle-circle" onMouseDown={this.mouseDown} ref={(handleCircle) => { this.handleCircle = handleCircle }} />
</div>
And this is my CSS:
#ap-timeline{
display: flex;
flex-direction: row;
align-items: center;
width: 550px;
height: 4px;
border-radius: 15px;
background: $audio-slider-gray;
margin: 0 10px;
#ap-handle {
background: $white;
height: 4px;
}
#ap-handle-circle {
width: 13px;
height: 13px;
border-radius: 50%;
background: $white;
transform: scale(0.25);
}
}
}
The ap-handle is the progress bar that I'm trying to use to fill up the timeline as the song plays.
I'm not much of a react person so excuse any poor practices here:
#progress-bar {
height: 20px;
background: red;
transform: scaleX(0);
transform-origin: center left;
}
class App extends Component {
constructor() {
super();
this.interval = null; // setInterval
this.audioEl = new Audio(
"https://file-examples.com/wp-content/uploads/2017/11/file_example_MP3_700KB.mp3"
);
this.audioEl.addEventListener("canplaythrough", () => {
this.setState({
duration: this.audioEl.duration
});
});
this.state = {
currentTime: 0,
duration: 0
};
}
playAudio() {
this.audioEl.play();
// requestAnimationFrame would probably be better, but
// for example sake we'll use setInterval
this.interval = setInterval(() => {
this.setState({
currentTime: this.audioEl.currentTime,
progress: Math.ceil((this.audioEl.currentTime / this.audioEl.duration) * 100) / 100
});
}, 100);
}
stopAudio() {
clearInterval(this.interval);
this.audioEl.pause();
}
render() {
return (
<div>
<button onClick={() => this.playAudio()}>Play</button>
<button onClick={() => this.stopAudio()}>Stop</button>
<div
style={{ transform: `scaleX(${this.state.progress})` }}
id="progress-bar"
/>
</div>
);
}
}
Blitz