I have three input fields. Once for email, password and confirm password. If the user presses a button then a method checkAll() should be executed, this checks if the defaults for the fields are correct, if not another class should be assigned to the fields and the variable should be false.
The problem is, if all values are true then the default classes should be displayed again and console.log("Everything ok"); should be output. However, if I reload the page and don't fill anything (everything should be set to false) then I still get an Everything ok in the console and all the variables are set to true, even though the method has set the values to false. Why is that?
SignUp.js
import React, { useState } from "react";
import "./SignUp.css";
function SignUp() {
const [email, setEmail] = useState(true);
const [value_email, setValue_email] = useState('')
const [password, setPassword] = useState('')
const [passwordLength, setPasswordLenth] = useState(true)
const [passwordValueEqual, setPasswordValueEqual] = useState('')
const [passwordEqual, setPasswordEqual] = useState(true)
const [emailExist, setEmailExist] = useState(false)
const checkEmail = async () => {
// eslint-disable-next-line
var reg = /^([A-Za-z0-9_\-\.])+\#([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;
if (reg.test(value_email) === false) {
console.log("E-Mail is wrong");
setEmail(false);
}
else {
setEmail(true);
}
};
const checkPasswortLength = async () => {
if(password.length < 5 ) {
console.log("Passwort zu kurz");
setPasswordLenth(false);
}
else{
setPasswordLenth(true);
}
}
const checkPasswortEqual = async () => {
if(passwordValueEqual === password ) {
setPasswordEqual(true);
console.log("Passwörter sind gleich");
}
else{
console.log("Passwörter sind ungleich");
setPasswordEqual(false);
}
}
async function checkAll() {
await checkEmail();
await checkPasswortLength();
await checkPasswortEqual();
if(email === true && passwordLength === true && passwordEqual === true ) {
console.log("Everything ok");
setEmailExist(true);
}
}
return (
<div className="SignUp">
<div className="container" id="container">
<div className="form-container sign-in-container">
<form>
<h2>Registriere dich jetzt</h2>
<input type="email" className={email ? 'input-form' : 'input-form-validation-wrong'} placeholder="E-Mail" onChange={event => setValue_email(event.target.value)} />
<p className={email ? 'validation-email-right' : 'validation-email-wrong'}>E-Mail ist falsch</p>
<input type="password" id="password" className={passwordLength ? 'input-form' : 'input-form-validation-wrong'} placeholder="Passwort" onChange={event => setPassword(event.target.value)} />
<p className={passwordLength ? 'validation-password-short-right' : 'validation-password-short-wrong'}>Passwort ist zu kurz</p>
<input type="password" id="password-confirm" className={ passwordEqual ? 'input-form' : 'input-form-validation-wrong'} placeholder="Passwort bestätigen" onChange={event => setPasswordValueEqual(event.target.value)} />
<p className={passwordEqual ? 'validation-password-equal-right' : 'validation-password-equal-wrong'}>Passwörter stimmen nicht über ein</p>
{emailExist === true &&
<p className='validation-password-equal-wrong'>E-Mail gibt es bereits</p>
}
<div className='buttons-container'>
<button className="button-registration" type="button" onClick={checkAll}>Registrieren</button>
</div>
<a className="already-account" href="/login">Du hast bereits einen Account?</a>
</form>
</div>
<div className="overlay-container">
<div className="overlay">
<div className="overlay-panel overlay-right">
</div>
</div>
</div>
</div>
</div>
);
}
export default SignUp;
SignUp.css
#import url('https://fonts.googleapis.com/css?family=Montserrat:400,800');
* {
box-sizing: border-box;
}
body {
background: #fff;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
font-family: 'Montserrat', sans-serif;
height: 100vh;
}
h1 {
font-weight: bold;
margin: 0;
}
h2 {
text-align: center;
font-weight: normal;
font-size: 18px;
}
p {
font-size: 14px;
font-weight: 100;
line-height: 20px;
letter-spacing: 0.5px;
margin: 20px 0 30px;
}
span {
font-size: 12px;
}
a {
color: #333;
font-size: 14px;
text-decoration: none;
margin: 15px 0;
}
.buttons-container {
text-align: center;
display: flex;
margin-top: 35px;
}
.button-registration {
display: flex;
border-radius: 20px;
border: 1px solid #5869FF;
background-color: #5869FF;
color: #FFFFFF;
font-size: 12px;
font-weight: bold;
padding: 12px 25px;
letter-spacing: 1px;
text-transform: uppercase;
transition: transform 80ms ease-in;
}
button:active {
transform: scale(0.95);
}
button:focus {
outline: none;
}
button.ghost {
background-color: transparent;
border-color: #FFFFFF;
}
form {
background-color: #FFFFFF;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
padding: 0 50px;
height: 100%;
text-align: center;
}
.input-form {
background-color: #e6e6e6;
border: none;
border-radius: 20px;
padding: 12px 15px;
margin: 8px 0;
width: 100%;
}
.input-form:focus {
outline: none;
box-shadow: 0px 0px 2px #5869FF;
}
.input-form-validation-wrong {
background-color: #e6e6e6;
border: none;
border-radius: 20px;
padding: 12px 15px;
margin: 8px 0;
width: 100%;
}
.input-form-validation-wrong {
background-color: #e6e6e6;
border: none;
border-radius: 20px;
padding: 12px 15px;
margin: 8px 0;
width: 100%;
box-shadow: 0px 0px 4px #ff5858;
}
.input-form-validation-wrong:focus {
outline: none;
box-shadow: 0px 0px 4px #ff5858;
}
.input-form-validation-wrong::placeholder {
color: #ff5858;
}
.input-remain {
}
.optional-buttons {
text-align: center;
display: inline-block;
font-size: 12px;
}
.already-account {
color: #5869FF;
text-decoration: underline;
font-size: 12px;
text-align: center;
}
.already-account:hover {
color: #6a79fc;
text-decoration: underline;
}
.container {
position: absolute;
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
overflow: hidden;
}
.form-container {
position: absolute;
top: 0;
height: 100%;
max-width: 40%;
}
.sign-in-container {
left: 0;
width: 50%;
z-index: 2;
}
.container.right-panel-active .sign-in-container {
transform: translateX(100%);
}
.sign-up-container {
left: 0;
width: 50%;
opacity: 0;
z-index: 1;
}
.validation-wrong {
color: #ff5858;
margin-top: -2px;
margin-bottom: -2px;
text-align: left;
}
.validation-email-wrong {
color: #ff5858;
margin-top: -2px;
margin-bottom: -2px;
text-align: left;
}
.validation-email-right {
visibility: hidden;
margin-top: -2px;
margin-bottom: -2px;
}
.validation-password-short-wrong {
color: #ff5858;
margin-top: -2px;
margin-bottom: -2px;
text-align: left;
}
.validation-password-short-right {
visibility: hidden;
margin-top: -2px;
margin-bottom: -2px;
}
.validation-password-equal-wrong {
color: #ff5858;
margin-top: -2px;
margin-bottom: -2px;
text-align: left;
}
.validation-password-equal-right {
visibility: hidden;
margin-top: -2px;
margin-bottom: -2px;
}
.validation-right {
visibility: hidden;
margin-top: -2px;
margin-bottom: -2px;
}
.container.right-panel-active .sign-up-container {
transform: translateX(100%);
opacity: 1;
z-index: 5;
animation: show 0.6s;
}
#keyframes show {
0%, 49.99% {
opacity: 0;
z-index: 1;
}
50%, 100% {
opacity: 1;
z-index: 5;
}
}
.overlay-container {
position: absolute;
top: 0;
left: 40%;
width: 60%;
height: 100%;
overflow: hidden;
transition: transform 0.6s ease-in-out;
z-index: 100;
background-image: url('../../images/wallpaper.PNG');
background-position: center;
background-repeat: no-repeat;
background-size: cover;
}
.container.right-panel-active .overlay-container{
transform: translateX(-100%);
}
.overlay {
/*background: -webkit-linear-gradient(to right top, #5869ff, #5c66ff, #6063ff, #6460ff, #685dff, #6d5bff, #715aff, #7658ff, #7c58ff, #8158ff, #8658ff, #8b58ff);*/
/*background: linear-gradient(to right top, #5869ff, #5c66ff, #6063ff, #6460ff, #685dff, #6d5bff, #715aff, #7658ff, #7c58ff, #8158ff, #8658ff, #8b58ff);*/
background-repeat: no-repeat;
background-size: cover;
background-position: 0 0;
color: #FFFFFF;
position: relative;
left: -100%;
height: 100%;
width: 200%;
transform: translateX(0);
transition: transform 0.6s ease-in-out;
}
.container.right-panel-active .overlay {
transform: translateX(50%);
}
.overlay-panel {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
padding: 0 40px;
text-align: center;
top: 0;
height: 100%;
width: 50%;
transform: translateX(0);
transition: transform 0.6s ease-in-out;
}
.overlay-left {
transform: translateX(-20%);
}
.container.right-panel-active .overlay-left {
transform: translateX(0);
}
.overlay-right {
right: 0;
transform: translateX(0);
}
.container.right-panel-active .overlay-right {
transform: translateX(20%);
}
.social-container {
margin: 20px 0;
}
.social-container a {
border: 1px solid #DDDDDD;
border-radius: 50%;
display: inline-flex;
justify-content: center;
align-items: center;
margin: 0 5px;
height: 40px;
width: 40px;
}
#media screen and (max-width: 960px) {
.overlay {
visibility: hidden;
}
.overlay-container {
visibility: hidden;
}
.form-container {
max-width: 100%;
min-width: 100%;
}
.optional-buttons {
display: flex;
align-items: center;
}
}
App.js
import "./styles.css";
import SignUp from "./SignUp";
export default function App() {
return (
<div className="App">
<SignUp></SignUp>
</div>
);
}
Reload Page
First Button click (the yellow mark is wrong)
Second Button click this time without the print statement everything okay
Since you are calling a function that updates a state on another function, the following code on the same function block won't use the updated value.
what you can do is return the updated value whenever you set it:
const checkEmail = async () => {
// eslint-disable-next-line
var reg = /^([A-Za-z0-9_\-\.])+\#([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;
if (reg.test(value_email) === false) {
console.log("E-Mail is wrong");
setEmail(false);
return false; // add this one -------
}
else {
setEmail(true);
return true; // add this one --------
}
};
^^ do that for the other functions as well (checkPasswortLength, and checkPasswortEqual)
Then, on your checkAll:
async function checkAll() {
const emailCheck = await checkEmail();
const passwordLengthCheck = await checkPasswortLength();
const passwordEqualCheck = await checkPasswortEqual();
if(emailCheck === true && passwordLengthCheck === true && passwordEqualCheck === true ) {
console.log("Everything ok");
setEmailExist(true);
}
}
UPDATE
We can't use setstate callback functions since the setstate is called on another function, One other way is to use useEffect.
useEffect(() => {
if(email === true && passwordLength === true && passwordEqual === true ) {
console.log("Everything ok");
setEmailExist(true);
}
}, [])
async function checkAll() {
await checkEmail();
await checkPasswortLength();
await checkPasswortEqual();
// we removed the condition here
}
this way you don't have to "return" the value and will automatically notify the user everytime everything is ok.
Related
In my nextjs project everything works fine in desktop but in mobile (chorme) text is displayed sometimes and sometimes text is invisible. Here are the images Desktop Mobile-text-shown Mobile-text-not-shown
/* index.js */
import Head from "next/head";
import React from "react";
import MainContainer from "../components/Maincontainer";
import BookHoliday from "../components/BookHoliday";
export default function Home() {
return (
<>
<Head>
<title>Tourister</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<MainContainer />
<BookHoliday/>
<div className="test">
<h2>Just For Test</h2>
<p>
lorem400
</p>
</div>
</>
);
}
/* MainContainer.js */
import React, { useEffect, useRef } from "react";
import { FaAngleLeft, FaAngleRight } from "react-icons/fa";
import Navbar from "./Navbar";
import Header from "./Header";
import SideBar from "./SideBar";
import Image from "next/legacy/image";
function showAnimation() {
var count = 2;
let myInterval = null;
function runInterval() {
myInterval = setInterval(() => {
if (count === 3) {
document.getElementById("bg-3").style.opacity = "0";
document.getElementById("circle3").classList.remove("circle-ani");
document
.getElementById("small-circle3")
.classList.remove("small-circle-ani");
document.getElementById("bg-1").style.opacity = "1";
document.getElementById("circle1").classList.add("circle-ani");
document
.getElementById("small-circle1")
.classList.add("small-circle-ani");
count = 1;
} else if (count === 2) {
document.getElementById("bg-2").style.opacity = "0";
document.getElementById("circle2").classList.remove("circle-ani");
document
.getElementById("small-circle2")
.classList.remove("small-circle-ani");
document.getElementById("bg-3").style.opacity = "1";
document.getElementById("circle3").classList.add("circle-ani");
document
.getElementById("small-circle3")
.classList.add("small-circle-ani");
count = 3;
} else if (count === 1) {
document.getElementById("bg-1").style.opacity = "0";
document.getElementById("circle1").classList.remove("circle-ani");
document
.getElementById("small-circle1")
.classList.remove("small-circle-ani");
document.getElementById("bg-2").style.opacity = "1";
document.getElementById("circle2").classList.add("circle-ani");
document
.getElementById("small-circle2")
.classList.add("small-circle-ani");
count = 2;
}
}, 3000);
}
runInterval();
document.getElementById("circle1").addEventListener("click", () => {
changeBg("bg-1");
});
document.getElementById("circle2").addEventListener("click", () => {
changeBg("bg-2");
});
document.getElementById("circle3").addEventListener("click", () => {
changeBg("bg-3");
});
document.getElementById("left").addEventListener("click", () => {
changeBg("left");
});
document.getElementById("right").addEventListener("click", () => {
changeBg("right");
});
function changeBg(data) {
if (data === "bg-1") {
clearInterval(myInterval);
document.getElementById(`bg-${count}`).style.opacity = "0";
document.getElementById(`circle${count}`).classList.remove("circle-ani");
document
.getElementById(`small-circle${count}`)
.classList.remove("small-circle-ani");
document.getElementById("bg-1").style.opacity = "1";
document.getElementById("circle1").classList.add("circle-ani");
document
.getElementById("small-circle1")
.classList.add("small-circle-ani");
count = 1;
setTimeout(() => {
runInterval();
});
}
if (data === "bg-2") {
clearInterval(myInterval);
document.getElementById(`bg-${count}`).style.opacity = "0";
document.getElementById(`circle${count}`).classList.remove("circle-ani");
document
.getElementById(`small-circle${count}`)
.classList.remove("small-circle-ani");
document.getElementById("bg-2").style.opacity = "1";
document.getElementById("circle2").classList.add("circle-ani");
document
.getElementById("small-circle2")
.classList.add("small-circle-ani");
count = 2;
setTimeout(() => {
runInterval();
});
}
if (data === "bg-3") {
clearInterval(myInterval);
document.getElementById(`bg-${count}`).style.opacity = "0";
document.getElementById(`circle${count}`).classList.remove("circle-ani");
document
.getElementById(`small-circle${count}`)
.classList.remove("small-circle-ani");
document.getElementById("bg-3").style.opacity = "1";
document.getElementById("circle3").classList.add("circle-ani");
document
.getElementById("small-circle3")
.classList.add("small-circle-ani");
count = 3;
setTimeout(() => {
runInterval();
});
}
if (data === "left") {
clearInterval(myInterval);
if (count === 3) {
document.getElementById("bg-3").style.opacity = "0";
document.getElementById("circle3").classList.remove("circle-ani");
document
.getElementById("small-circle3")
.classList.remove("small-circle-ani");
document.getElementById("bg-2").style.opacity = "1";
document.getElementById("circle2").classList.add("circle-ani");
document
.getElementById("small-circle2")
.classList.add("small-circle-ani");
count = 2;
setTimeout(() => {
runInterval();
});
} else if (count === 2) {
document.getElementById("bg-2").style.opacity = "0";
document.getElementById("circle2").classList.remove("circle-ani");
document
.getElementById("small-circle2")
.classList.remove("small-circle-ani");
document.getElementById("bg-1").style.opacity = "1";
document.getElementById("circle1").classList.add("circle-ani");
document
.getElementById("small-circle1")
.classList.add("small-circle-ani");
count = 1;
setTimeout(() => {
runInterval();
});
} else if (count === 1) {
document.getElementById("bg-1").style.opacity = "0";
document.getElementById("circle1").classList.remove("circle-ani");
document
.getElementById("small-circle1")
.classList.remove("small-circle-ani");
document.getElementById("bg-3").style.opacity = "1";
document.getElementById("circle3").classList.add("circle-ani");
document
.getElementById("small-circle3")
.classList.add("small-circle-ani");
count = 3;
setTimeout(() => {
runInterval();
});
}
}
if (data === "right") {
clearInterval(myInterval);
if (count === 3) {
document.getElementById("bg-3").style.opacity = "0";
document.getElementById("circle3").classList.remove("circle-ani");
document
.getElementById("small-circle3")
.classList.remove("small-circle-ani");
document.getElementById("bg-1").style.opacity = "1";
document.getElementById("circle1").classList.add("circle-ani");
document
.getElementById("small-circle1")
.classList.add("small-circle-ani");
count = 1;
setTimeout(() => {
runInterval();
});
} else if (count === 2) {
document.getElementById("bg-2").style.opacity = "0";
document.getElementById("circle2").classList.remove("circle-ani");
document
.getElementById("small-circle2")
.classList.remove("small-circle-ani");
document.getElementById("bg-3").style.opacity = "1";
document.getElementById("circle3").classList.add("circle-ani");
document
.getElementById("small-circle3")
.classList.add("small-circle-ani");
count = 3;
setTimeout(() => {
runInterval();
});
} else if (count === 1) {
document.getElementById("bg-1").style.opacity = "0";
document.getElementById("circle1").classList.remove("circle-ani");
document
.getElementById("small-circle1")
.classList.remove("small-circle-ani");
document.getElementById("bg-2").style.opacity = "1";
document.getElementById("circle2").classList.add("circle-ani");
document
.getElementById("small-circle2")
.classList.add("small-circle-ani");
count = 2;
setTimeout(() => {
runInterval();
});
}
}
}
}
const Maincontainer = () => {
const dataFetchedRef = useRef(false);
useEffect(() => {
if (dataFetchedRef.current) return;
dataFetchedRef.current = true;
showAnimation();
}, []);
return (
<div id="nav-head">
<Image
layout="fill"
objectFit="cover"
src="/london.jpg"
className="bg"
id="bg-1"
/>
<Image
layout="fill"
objectFit="cover"
src="/rome.jpg"
className="bg"
id="bg-2"
/>
<Image
layout="fill"
objectFit="cover"
src="/paris.jpg"
className="bg"
id="bg-3"
/>
<div className="nav-head-container">
<SideBar />
<Navbar />
<Header />
<div className="circle-list">
<div className="circle" id="circle1">
<div className="small-circle" id="small-circle1"></div>
</div>
<div className="circle circle-ani" id="circle2">
<div
className="small-circle small-circle-ani"
id="small-circle2"
></div>
</div>
<div className="circle" id="circle3">
<div className="small-circle" id="small-circle3"></div>
</div>
</div>
<FaAngleLeft
className="left-arrow"
id="left"
color="#fff"
fontSize={20}
/>
<FaAngleRight
className="right-arrow"
id="right"
color="#fff"
fontSize={20}
/>
</div>
</div>
);
};
export default Maincontainer;
/* BookHoliday.js */
import React, { useState } from "react";
import Datetime from "react-datetime";
import "react-datetime/css/react-datetime.css";
import { AiFillCalendar, AiOutlineSearch } from "react-icons/ai";
import moment from "moment";
const BookHoliday = () => {
var yesterday = moment().subtract(1, "day");
const [arrivalDate, setArrivalDate] = useState(null);
const [departureDate, setDepartureDate] = useState(null);
var Arrivalvalid = function (current) {
return current.isAfter(yesterday);
};
var Departurevalid = function (current) {
if (arrivalDate !== null) {
return current >= arrivalDate;
} else {
return current.isAfter(yesterday);
}
};
return (
<div className="book-holiday">
<h2>Book Holiday Now!</h2>
<form className="form-container">
<div className="input-container">
<input
type="search"
name="search"
id="search"
placeholder="Search Your Destination"
/>
<div className="icon" id="search-icon">
{" "}
<AiOutlineSearch color="#bd7457" fontSize={20} />
</div>
</div>
<div className="input-container">
<Datetime
timeFormat={false}
dateFormat="DD MMM YYYY"
isValidDate={Arrivalvalid}
value={arrivalDate}
onChange={(e) => {
setArrivalDate(e);
if (departureDate !== null && e > departureDate) {
setDepartureDate(e);
}
}}
inputProps={{ placeholder: "Arrival", readOnly: true }}
/>{" "}
<div className="icon pointer" id="a-icon">
{" "}
<AiFillCalendar color="#bd7457" fontSize={20} />
</div>
</div>
<div className="input-container">
<Datetime
timeFormat={false}
dateFormat="DD MMM YYYY"
isValidDate={Departurevalid}
value={departureDate}
onChange={(e) => {
setDepartureDate(e);
}}
inputProps={{ placeholder: "Departure", readOnly: true }}
/>{" "}
<div className="icon pointer" id="d-icon">
{" "}
<AiFillCalendar color="#bd7457" fontSize={20} />
</div>
</div>
<button id="search-btn">Search</button>
</form>
</div>
);
};
export default BookHoliday;
/* globals.css */
#import url("https://fonts.googleapis.com/css2?family=Ubuntu&display=swap");
#import url("https://fonts.googleapis.com/css2?family=Montserrat&display=swap");
#import url("https://fonts.googleapis.com/css2?family=Lobster&family=Ubuntu&display=swap");
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body {
font-family: "Montserrat";
}
#nav-head {
position: relative;
width: 100%;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.nav-head-container {
position: absolute;
top: 0;
left: 0;
background-color: rgba(0, 0, 0, 0.5);
width: 100%;
height: 100%;
}
#bg-1,
#bg-3 {
opacity: 0;
}
.bg {
transition: 0.6s;
}
.left-arrow {
position: absolute;
left: 10px;
top: 50%;
border: 1px solid white;
border-radius: 4px;
cursor: pointer;
}
.right-arrow {
position: absolute;
right: 10px;
top: 50%;
border: 1px solid white;
border-radius: 4px;
cursor: pointer;
}
.circle-list {
position: absolute;
bottom: 10px;
left: 0;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.circle {
width: 20px;
height: 20px;
border-radius: 20px;
background-color: gray;
opacity: 0.5;
margin: 0px 10px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
.circle-ani {
border: 2px solid white;
background-color: transparent;
opacity: 1;
}
.small-circle {
width: 6px;
height: 6px;
border-radius: 6px;
}
.small-circle-ani {
background-color: white;
}
.navbar {
height: 75px;
display: flex;
justify-content: center;
align-items: center;
}
.logo {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
}
.navbar .navlink {
display: flex;
flex: 4;
justify-content: space-evenly;
align-items: center;
}
.link {
text-decoration: none;
color: white;
transition: 0.6s;
}
.navlink li {
list-style: none;
}
.cta {
display: flex;
justify-content: center;
align-items: center;
flex: 1;
}
.cta-button {
border: none;
background-color: transparent;
color: white;
cursor: pointer;
padding: 10px;
border-bottom: 1px solid white;
transition: 0.6s;
}
#ham {
position: relative;
display: none;
height: 24px;
width: 24px;
margin-left: 10px;
cursor: pointer;
}
#header {
width: 100%;
height: calc(100vh - 75px);
display: flex;
color: white;
flex-direction: column;
justify-content: center;
}
#header h2 {
font-size: 4rem;
padding: 0px 10% 10px;
}
#header p {
padding: 0px 10% 20px;
line-height: 25px;
}
#header-btn {
padding: 10px 20px;
background-color: #bd7457;
color: white;
border: 1px solid #bd7457;
width: 180px;
margin-left: 10%;
border-radius: 6px;
transition: 0.6s;
}
#sidebar {
position: fixed;
bottom: 0;
left: 0;
width: 200px;
height: 100%;
background-color: black;
color: white;
z-index: 5;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
transform: translateX(-200px);
transition: 0.6s;
}
#cross {
position: absolute;
right: 10px;
top: 10px;
width: 14px;
height: 14px;
cursor: pointer;
}
.sidelink {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
width: 100%;
height: 226px;
}
.sidelink .link {
font-size: 1.4rem;
}
.side-cta {
height: 56px;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
#side-button {
padding: 10px 20px;
border: none;
background-color: transparent;
border: 1px solid white;
border-radius: 6px;
cursor: pointer;
color: white;
transition: 0.6s;
}
.book-holiday {
background-color: rgb(245, 245, 245);
padding: 30px 0px;
max-height: 9999px;
}
.book-holiday h2 {
font-size: 2rem;
text-align: center;
margin-bottom: 20px;
}
.form-control,
#search {
width: 250px;
padding: 10px 20px;
outline: none;
}
.form-container {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
#search-btn {
background-color: #bd7457;
border: 1px solid #bd7457;
color: white;
padding: 10px 20px;
transition: 0.6s;
margin: 10px 0px 20px;
}
.input-container {
display: flex;
position: relative;
margin: 10px 0px;
}
.icon {
position: absolute;
right: 0px;
top: 0px;
padding: 7px 10px;
}
.test {
background-color: rgb(255, 235, 205);
padding: 30px 0px;
max-height: 9999px;
}
.test h2 {
margin-bottom: 20px;
text-align: center;
font-size: 2rem;
}
/* This is for react date-time library */
.rdtDay {
color: #bd7457 !important;
}
.rdtDisabled {
color: #999999 !important;
}
.rdtOld {
color: #999999 !important;
}
.rdtNew {
color: black !important;
}
.rdtActive,
.rdtActive:hover {
background-color: #bd7457 !important;
color: white !important;
}
.rdtPicker td.rdtToday:before {
border-bottom-color: #bd7457 !important;
}
#media (hover: hover) {
.cta-button:hover {
border-color: #bd7457;
color: #bd7457;
}
.navlink li .link:hover {
color: #bd7457;
}
#side-button:hover {
color: #bd7457;
border-color: #bd7457;
}
#header-btn:hover,
#search-btn:hover {
border: 1px solid #bd7457;
background-color: transparent;
color: #bd7457;
}
}
#media (hover: none) {
.cta-button:active {
border-bottom: none;
color: #bd7457;
}
.navlink li .link:active {
color: #bd7457;
}
#side-button:active {
color: #bd7457;
border-color: #bd7457;
}
#header-btn:active,
#search-btn:active {
border: 1px solid #bd7457;
color: #bd7457;
background-color: transparent;
}
}
#media (max-width: 768px) {
.logo {
justify-content: flex-end;
margin-right: 30px;
}
#ham {
display: flex;
}
.navbar .navlink,
.cta {
display: none;
}
.navbar {
height: 40px;
align-items: flex-end;
}
#header {
height: calc(100vh -40px);
}
#header h2 {
font-size: 3rem;
}
}
#media (max-width: 540px) {
#header h2 {
font-size: 2.6rem;
}
}
#media (max-height: 414px) {
.sidelink .link {
font-size: 1.2rem;
}
#header h2 {
font-size: 2.4rem;
padding-bottom: 5px;
}
#header p {
padding-bottom: 10px;
}
}
I have tried almost everything, earlier I thought it was because of changing the background images using setInterval or due to react-date-time library, so I made a static background image, removed that date-time library and even removed the background image, but it didn't solve the problem. I am able to click the text when it is invisible, but when i refresh the page it appears.
I have looked about for quite a while now, and nothing seems to work or fit my current problem.
I am creating a To-Do project and I want chrome to remember your To-Dos, even if you refresh the browser.
I have tried a few methods, but this is where I am at the moment. The javascript updates the localstorage everytime the 'createPost' function is called, and then it is loaded by the code at the bottom (i flagged the relavant lines).
View the updated demo here: https://dominicody.netlify.app/todo/
( the main website isnt finished so dont judge please lol)
let form = document.getElementById("todo-form");
let input = document.getElementById("input");
let msg = document.getElementById("msg");
let todos = document.getElementById("todos");
let right = document.getElementById("right")
let noToDo = document.getElementById("notodo")
let counter = 0; /* new line */
form.addEventListener("submit", e => {
e.preventDefault();
console.log("button clicked");
formValidation();
});
let formValidation = () => {
if (input.value === "") {
msg.innerHTML = "To-Do cannot be blank";
} else {
msg.innerHTML = "";
acceptData();
}
};
let data = [];
let acceptData = () => {
data = [...data, {
"text": input.value
}];
input.value = "";
localStorage.setItem('todoItemsRef', JSON.stringify(data));
createToDo(data[data.length - 1]);
};
let createToDo = () => {
counter++; /* new line */
todos.innerHTML += `
<div>
<p>${data.text}</p>
<span class="options">
<i onClick="completedToDo(this)" class='bx bx-check-circle'></i>
<i onClick="editToDo(this)" class='bx bx-edit-alt'></i>
<i onClick="deleteToDo(this)" class='bx bx-trash' ></i>
</span>
</div>
`;
input.value = "";
};
let completedToDo = (e) => {
e.parentElement.parentElement.classList.toggle('completed')
}
let deleteToDo = (e) => {
e.parentElement.parentElement.remove();
counter--;
if (counter === 0) {
showNoToDo();
}
};
let editToDo = (e) => {
input.value = e.parentElement.previousElementSibling.innerHTML;
e.parentElement.parentElement.remove();
};
let hideNoToDo = () => {
noToDo.classList.add('hidden')
form.classList.remove('form-reg-position')
}
let showNoToDo = () => {
noToDo.classList.remove('hidden')
form.classList.add('form-reg-position')
}
/* localStorage.setItem('todoItemsRef', JSON.stringify(data)); */
document.addEventListener('DOMContentLoaded', () => {
const ref = localStorage.getItem('todoItemsRef');
if (ref) {
data = JSON.parse(ref);
data.forEach(t => {
createToDo(t);
});
}
});
#import url('https://fonts.googleapis.com/css2?family=Kanit:wght#100;200;300;400;500;600;700;800;900&family=League+Spartan:wght#200;300;400;500;600;700;800;900&family=Noto+Serif:wght#400;700&display=swap');
:root {
--primary-color: rgb(240, 69, 114);
--primary-color-ligh: rgb(220, 100, 124);
--secondary-color: rgb(25, 24, 44);
--secondary-color-ligh: rgb(64, 64, 83);
--white: rgb(255, 255, 255);
--primary-font: 'League Spartan', sans-serif;
--secondary-font: 'Noto Serif', serif;
--kanit-font: 'Kanit', sans-serif;
--swiper-theme-color: rgb(240, 69, 114);
}
* {
padding: 0;
margin: 0;
box-sizing: border-box;
font-family: 'League Spartan', sans-serif;
}
body {
width: 100%;
background-color: #fff;
max-height: 100%;
overflow: hidden;
}
h1 {
position: absolute;
top: 10%;
left: 50%;
transform: translateX(-50%);
font-size: 4rem;
opacity: 0.5;
}
.todo-main {
position: absolute;
top: 30%;
left: 50%;
width: 75%;
height: 70%;
transform: translateX(-50%);
overflow-y: auto;
overflow-x: hidden;
}
.no-todo {
position: absolute;
top: 23%;
left: 50%;
transform: translateX(-50%);
text-align: center;
}
.no-todo img {
width: 150px;
}
.no-todo p {
font-family: var(--primary-font);
font-weight: 600;
font-size: 1.3rem;
margin-top: 24px;
margin-bottom: 10px;
}
.no-todo p2 {
font-family: var(--primary-font);
font-weight: 600;
font-size: 1.2rem;
color: #000;
opacity: 0.6;
}
.no-todo.hidden {
display: none;
}
form {
margin-left: 100%;
transform: translateX(-50%);
width: 100%;
;
}
.textarea {
margin-top: 24px;
min-width: 95%;
max-width: 95%;
min-height: 40px;
max-height: 40px;
display: flex;
outline: none;
border: none;
border: 2px solid var(--secondary-color);
border-radius: 5px;
padding: 10px;
font-size: 20px;
font-weight: 400;
margin-top: 5px;
color: #000;
transform: translateX(-50%);
overflow-y: hidden;
transition-duration: 0.7s;
}
.form-reg-position {
transform: translateY(250%) translateX(-50%);
transition-duration: 0.7s;
}
.textarea::placeholder {
color: #000;
}
#todos div {
display: flex;
align-items: center;
justify-content: space-between;
transform: translateX(-50%);
font-family: var(--kanit-font);
font-weight: 500;
font-size: 1.2rem;
margin-bottom: 15px;
width: 95%;
margin-left: 50%;
border: 2px solid var(--secondary-color);
border-radius: 5px;
padding: 8px;
color: #000;
}
#todos div.completed {
background-color: #00FF00;
color: #000;
}
.options {
display: flex;
gap: 25px;
color: var(--secondary-color);
}
i {
cursor: pointer;
}
#msg {
position: absolute;
left: 0%;
transform: translateX(-50%);
color: red;
margin-bottom: 10px;
}
/* TOP NAV */
.top-nav-img-only {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 8%;
display: flex;
align-items: center;
z-index: 99;
}
.top-nav-img-only a img {
position: absolute;
top: 20%;
left: 5%;
height: 60%;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>bydom</title>
<link rel="stylesheet" href="/todo/todo.css">
<link href='https://unpkg.com/boxicons#2.1.2/css/boxicons.min.css' rel='stylesheet'>
</head>
<body>
<div class="top-nav-img-only">
<a href="/">
<img src="/images/logo.png">
</a>
</div>
<h1>todos</h1>
<div class="no-todo" id="notodo">
<img src="/svg/checklist.svg">
<p>Add your first To-Do</p>
<p2>What's first on your list?</p2>
</div>
<div class="todo-main">
<form class="form-reg-position" id="todo-form">
<br><br>
<input class="textarea" type="text" name="todo" id="input" placeholder="Add new To-do"></input>
<br><br>
<div id="msg"></div>
</form>
<div id="todos">
</div>
</div>
<script src="/todo/todo.js"></script>
</body>
</html>
The first thing you need to do is update Local Storage whenever you change something in the tasks.
You can create a function to update Local Storage and this function will be called whenever you update something in the tasks, when you mark it as complete and when you delete a task.
const updateLocalStorage = () => {
}
This function will pick up all the tasks that are on the screen
Add task in Local Storage
localStorage.setItem("tasks", JSON.stringify([...JSON.parse(localStorage.getItem("tasks") || "[]"), { task: task.value, completed: false }]));
There seem to be a few issues with the example/code provided, but the main thing seems to be how you were trying to store the todo list. Your list was an array called data and when each new todo item was created, it would overwrite the previous value of data["text"]. Also, because of the way you structured your array (which was more like an object), you could not stringify the list.
So the first fix was to make your array an array of objects [{},{}]. This would allow each item on the list to have its own text value.
Another issue was calling a function that didn't exist renderTodo. (OP did mention in a comment this was later corrected).
There are also a lot of weird ways some of the code is structured, but with all of that aside, here is a basic example of what you seem to need. I removed functions and code that wasn't necessary for the question asked. This solution will save your todo list in localStorage and load it when the page loads. Anything else is a different question/topic.
let form = document.getElementById("todo-form"),
input = document.getElementById("input"),
msg = document.getElementById("msg"),
todos = document.getElementById("todos"),
data = [];
form.addEventListener("submit", e => {
e.preventDefault();
formValidation();
});
let formValidation = () => {
if (input.value === "") {
msg.innerHTML = "To-Do cannot be blank";
} else {
msg.innerHTML = "";
acceptData();
}
};
let acceptData = () => {
data = [...data, {"text": input.value}];
input.value = "";
localStorage.setItem('todoItemsRef', JSON.stringify(data));
renderTodo(data[data.length-1]);
};
let renderTodo = (t) => {
let todo = document.createElement("div");
todo.innerHTML = `<p>${t.text}</p>`;
todos.append(todo);
};
document.addEventListener('DOMContentLoaded', () => {
const ref = localStorage.getItem('todoItemsRef');
if(ref) {
data = JSON.parse(ref);
data.forEach(t => {
renderTodo(t);
});
}
});
:root {
--primary-color: rgb(240, 69, 114);
--primary-color-ligh: rgb(220, 100, 124);
--secondary-color: rgb(25, 24, 44);
--secondary-color-ligh: rgb(64, 64, 83);
--white: rgb(255, 255, 255);
--primary-font: 'League Spartan', sans-serif;
--secondary-font: 'Noto Serif', serif;
--kanit-font: 'Kanit', sans-serif;
--swiper-theme-color: rgb(240, 69, 114);
}
* {
padding: 0;
margin: 0;
box-sizing: border-box;
font-family: 'League Spartan', sans-serif;
}
body {
width: 100%;
background-color: #fff;
max-height: 100%;
overflow: hidden;
}
h1 {
position: absolute;
top: 10%;
left: 50%;
transform: translateX(-50%);
font-size: 4rem;
opacity: 0.5;
}
.todo-main {
position: absolute;
top: 30%;
left: 50%;
width: 75%;
height: 70%;
transform: translateX(-50%);
overflow-y: auto;
overflow-x: hidden;
}
.textarea {
margin-top: 24px;
min-width: 95%;
max-width: 95%;
min-height: 40px;
max-height: 40px;
display: flex;
outline: none;
border: none;
border: 2px solid var(--secondary-color);
border-radius: 5px;
padding: 10px;
font-size: 20px;
font-weight: 400;
margin-top: 5px;
color: #000;
transform: translateX(-50%);
overflow-y: hidden;
transition-duration: 0.7s;
}
form {
margin-left: 100%;
transform: translateX(-50%);
width: 100%;
}
#todos div {
display: flex;
align-items: center;
justify-content: space-between;
transform: translateX(-50%);
font-family: var(--kanit-font);
font-weight: 500;
font-size: 1.2rem;
margin-bottom: 15px;
width: 95%;
margin-left: 50%;
border: 2px solid var(--secondary-color);
border-radius: 5px;
padding: 8px;
color: #000;
}
<h1>todos</h1>
<div class="todo-main">
<form class="form-reg-position" id="todo-form">
<br><br>
<input class="textarea" type="text" name="todo" id="input" placeholder="Add new To-do">
<br><br>
<div id="msg"></div>
</form>
<div id="todos"></div>
</div>
NOTE This example will not run on Stack Overflow because of a lack of access to the localStorage object.
EDIT I also have a relevant CodePen with more of the original code for reference. localStorage will function in this CodePen example.
I am working on an audio player with Vue 3 and the Napster API.
Project details
I have made the vinyl spin with the help of a CSS keyframes-based animation and the isSpinning computed property.
I want the vinyl to stop spinning once the end of the current track is reached, which is why isSpinning has this "formula":
isSpinning() {
return this.isPlaying && !this.player.ended;
}
const musicApp = {
data() {
return {
player: new Audio(),
trackCount: 0,
tracks: [],
muted: false,
isPlaying: false
};
},
methods: {
async getTracks() {
try {
const response = await axios
.get(
"https://api.napster.com/v2.1/tracks/top?apikey=ZTk2YjY4MjMtMDAzYy00MTg4LWE2MjYtZDIzNjJmMmM0YTdm"
)
.catch((error) => {
console.log(error);
});
this.tracks = response;
this.tracks = response.data.tracks;
} catch (error) {
console.log(error);
}
},
nextTrack() {
if (this.trackCount < this.tracks.length - 1) {
this.trackCount++;
this.setPlayerSource();
this.playPause();
}
},
prevTrack() {
if (this.trackCount >= 1) {
this.trackCount--;
this.setPlayerSource();
this.playPause();
}
},
setPlayerSource() {
this.player.src = this.tracks[this.trackCount].previewURL;
},
playPause() {
if (this.player.paused) {
this.isPlaying = true;
this.player.play();
} else {
this.isPlaying = false;
this.player.pause();
}
},
toggleMute() {
this.player.muted = !this.player.muted;
this.muted = this.player.muted;
}
},
async created() {
await this.getTracks();
this.setPlayerSource();
},
computed: {
isSpinning() {
return this.isPlaying && !this.player.ended;
}
}
};
Vue.createApp(musicApp).mount("#audioPlayer");
html,
body {
margin: 0;
padding: 0;
font-size: 16px;
}
body * {
box-sizing: border-box;
font-family: "Montserrat", sans-serif;
}
#-webkit-keyframes spin {
0% {
-webkit-transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
}
}
#keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.player-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background-color: #2998ff;
background-image: linear-gradient(62deg, #2998ff 0%, #5966eb 100%);
}
#audioPlayer {
width: 300px;
height: 300px;
border-radius: 8px;
position: relative;
overflow: hidden;
background-color: #00ca81;
background-image: linear-gradient(160deg, #00ca81 0%, #ffffff 100%);
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
display: flex;
flex-direction: column;
align-items: center;
}
.volume {
color: #ff0057;
opacity: 0.9;
display: inline-block;
width: 20px;
font-size: 20px;
position: absolute;
top: 5px;
right: 6px;
cursor: pointer;
}
.album {
width: 100%;
flex: 1;
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
}
.album-items {
padding: 0 10px;
text-align: center;
}
.cover {
width: 150px;
height: 150px;
margin: auto;
box-shadow: 0px 5px 12px 0px rgba(0, 0, 0, 0.17);
border-radius: 50%;
background: url("https://w7.pngwing.com/pngs/710/955/png-transparent-vinyl-record-artwork-phonograph-record-compact-disc-lp-record-disc-jockey-symbol-miscellaneous-classical-music-sound.png") center top transparent;
background-size: cover;
}
.cover.spinning {
webkit-animation: spin 6s linear infinite;
/* Safari */
animation: spin 6s linear infinite;
}
.info {
width: 100%;
padding-top: 5px;
color: #000;
opacity: 0.85;
}
.info h1 {
font-size: 11px;
margin: 5px 0 0 0;
}
.info h2 {
font-size: 10px;
margin: 3px 0 0 0;
}
.to-bottom {
width: 100%;
margin-top: auto;
display: flex;
flex-wrap: wrap;
justify-content: center;
}
.track {
background-color: #ff0057;
opacity: 0.9;
height: 3px;
width: 100%;
}
.controls {
width: 150px;
display: flex;
height: 60px;
justify-content: space-between;
align-items: center;
}
.controls .navigate {
display: flex;
box-shadow: 1px 2px 7px 2px rgba(0, 0, 0, 0.09);
width: 33px;
height: 33px;
line-height: 1;
color: #ff0057;
cursor: pointer;
background: #fff;
opacity: 0.9;
border-radius: 50%;
text-align: center;
justify-content: center;
align-items: center;
}
.controls .navigate.disabled {
pointer-events: none;
color: #606060;
background-color: #f7f7f7;
}
.controls .navigate.navigate-play {
width: 38px;
height: 38px;
}
.navigate-play .fa-play {
margin-left: 3px;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
<script src="https://unpkg.com/axios#0.22.0/dist/axios.min.js"></script>
<script src="https://unpkg.com/vue#next"></script>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght#300;500&display=swap" rel="stylesheet">
<div class="player-container">
<div id="audioPlayer">
<span class="volume" #click="toggleMute">
<i v-show="!muted" class="fa fa-volume-up"></i>
<i v-show="muted" class="fa fa-volume-off"></i>
</span>
<div class="album">
<div class="album-items">
<div class="cover" :class="{'spinning' : isSpinning}"></div>
<div class="info">
<h1>{{tracks[trackCount].name}}</h1>
<h2>{{tracks[trackCount].artistName}}</h2>
</div>
</div>
</div>
<div class="to-bottom">
<div class="track"></div>
<div class="controls">
<div class="navigate navigate-prev" :class="{'disabled' : trackCount == 0}" #click="prevTrack">
<i class="fa fa-step-backward"></i>
</div>
<div class="navigate navigate-play" #click="playPause">
<i v-show="!isPlaying" class="fa fa-play"></i>
<i v-show="isPlaying" class="fa fa-pause"></i>
</div>
<div class="navigate navigate-next" :class="{'disabled' : trackCount == tracks.length - 1}" #click="nextTrack">
<i class="fa fa-step-forward"></i>
</div>
</div>
</div>
</div>
</div>
The problem
But, to my surprise, the app is unaware of the fact that the value of this.player.ended has changed (or is supposed to).
What am I doing wrong?
An Audio object will not function in the same manner as normal JavaScript objects in Vue, as the internals that Vue uses to abstract away state change observation will not be maintained when the Audio object changes state. In other words, the thing that allows Vue to detect the Audio object switching from ended === false to ended === true won't work, preventing Vue from knowing that the component needs to be updated.
If you wish to observe a change in ended state, then you'll want to add a custom event listener to the object in your created hook to toggle the spinning state and simply remove the ended check:
const musicApp = {
data() {
return {
player: new Audio(),
trackCount: 0,
tracks: [],
muted: false,
isPlaying: false
};
},
methods: {
async getTracks() {
try {
const response = await axios
.get(
"https://api.napster.com/v2.1/tracks/top?apikey=ZTk2YjY4MjMtMDAzYy00MTg4LWE2MjYtZDIzNjJmMmM0YTdm"
)
.catch((error) => {
console.log(error);
});
this.tracks = response;
this.tracks = response.data.tracks;
} catch (error) {
console.log(error);
}
},
nextTrack() {
if (this.trackCount < this.tracks.length - 1) {
this.trackCount++;
this.setPlayerSource();
this.playPause();
}
},
prevTrack() {
if (this.trackCount >= 1) {
this.trackCount--;
this.setPlayerSource();
this.playPause();
}
},
setPlayerSource() {
this.player.src = this.tracks[this.trackCount].previewURL;
},
playPause() {
if (this.player.paused) {
this.isPlaying = true;
this.player.play();
} else {
this.isPlaying = false;
this.player.pause();
}
},
toggleMute() {
this.player.muted = !this.player.muted;
this.muted = this.player.muted;
}
},
async created() {
await this.getTracks();
this.setPlayerSource();
this.player.addEventListener('ended', () => {
this.isPlaying = false;
});
},
computed: {
isSpinning() {
return this.isPlaying;
}
}
};
Vue.createApp(musicApp).mount("#audioPlayer");
html,
body {
margin: 0;
padding: 0;
font-size: 16px;
}
body * {
box-sizing: border-box;
font-family: "Montserrat", sans-serif;
}
#-webkit-keyframes spin {
0% {
-webkit-transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
}
}
#keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.player-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background-color: #2998ff;
background-image: linear-gradient(62deg, #2998ff 0%, #5966eb 100%);
}
#audioPlayer {
width: 300px;
height: 300px;
border-radius: 8px;
position: relative;
overflow: hidden;
background-color: #00ca81;
background-image: linear-gradient(160deg, #00ca81 0%, #ffffff 100%);
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
display: flex;
flex-direction: column;
align-items: center;
}
.volume {
color: #ff0057;
opacity: 0.9;
display: inline-block;
width: 20px;
font-size: 20px;
position: absolute;
top: 5px;
right: 6px;
cursor: pointer;
}
.album {
width: 100%;
flex: 1;
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
}
.album-items {
padding: 0 10px;
text-align: center;
}
.cover {
width: 150px;
height: 150px;
margin: auto;
box-shadow: 0px 5px 12px 0px rgba(0, 0, 0, 0.17);
border-radius: 50%;
background: url("https://w7.pngwing.com/pngs/710/955/png-transparent-vinyl-record-artwork-phonograph-record-compact-disc-lp-record-disc-jockey-symbol-miscellaneous-classical-music-sound.png") center top transparent;
background-size: cover;
}
.cover.spinning {
webkit-animation: spin 6s linear infinite;
/* Safari */
animation: spin 6s linear infinite;
}
.info {
width: 100%;
padding-top: 5px;
color: #000;
opacity: 0.85;
}
.info h1 {
font-size: 11px;
margin: 5px 0 0 0;
}
.info h2 {
font-size: 10px;
margin: 3px 0 0 0;
}
.to-bottom {
width: 100%;
margin-top: auto;
display: flex;
flex-wrap: wrap;
justify-content: center;
}
.track {
background-color: #ff0057;
opacity: 0.9;
height: 3px;
width: 100%;
}
.controls {
width: 150px;
display: flex;
height: 60px;
justify-content: space-between;
align-items: center;
}
.controls .navigate {
display: flex;
box-shadow: 1px 2px 7px 2px rgba(0, 0, 0, 0.09);
width: 33px;
height: 33px;
line-height: 1;
color: #ff0057;
cursor: pointer;
background: #fff;
opacity: 0.9;
border-radius: 50%;
text-align: center;
justify-content: center;
align-items: center;
}
.controls .navigate.disabled {
pointer-events: none;
color: #606060;
background-color: #f7f7f7;
}
.controls .navigate.navigate-play {
width: 38px;
height: 38px;
}
.navigate-play .fa-play {
margin-left: 3px;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
<script src="https://unpkg.com/axios#0.22.0/dist/axios.min.js"></script>
<script src="https://unpkg.com/vue#next"></script>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght#300;500&display=swap" rel="stylesheet">
<div class="player-container">
<div id="audioPlayer">
<span class="volume" #click="toggleMute">
<i v-show="!muted" class="fa fa-volume-up"></i>
<i v-show="muted" class="fa fa-volume-off"></i>
</span>
<div class="album">
<div class="album-items">
<div class="cover" :class="{'spinning' : isSpinning}"></div>
<div class="info">
<h1>{{tracks[trackCount].name}}</h1>
<h2>{{tracks[trackCount].artistName}}</h2>
</div>
</div>
</div>
<div class="to-bottom">
<div class="track"></div>
<div class="controls">
<div class="navigate navigate-prev" :class="{'disabled' : trackCount == 0}" #click="prevTrack">
<i class="fa fa-step-backward"></i>
</div>
<div class="navigate navigate-play" #click="playPause">
<i v-show="!isPlaying" class="fa fa-play"></i>
<i v-show="isPlaying" class="fa fa-pause"></i>
</div>
<div class="navigate navigate-next" :class="{'disabled' : trackCount == tracks.length - 1}" #click="nextTrack">
<i class="fa fa-step-forward"></i>
</div>
</div>
</div>
</div>
</div>
Try to add ended event instead
https://developer.mozilla.org/en-US/docs/Web/API/HTMLAudioElement#events
const audioElement = new Audio('car_horn.wav');
audioElement.addEventListener('ended', () => {
this.isPlaying = false
})
I am doing a twitch web app. I try to use the Intersection observer API to achieve infinite scrolling and it works. However, I notice that my navbar just disappears at all when the infinite scrolling is working. I guess the issue is caused by "DOMContentLoaded", but not sure. Thank you.
Here is my codepen link : Twitch
const clientID = '5npghe3kytuifte3z9kvwnto50mqch';
const req = new XMLHttpRequest();
function showError() {
alert('Error');
}
function getResp(url, callback) {
req.open('GET', url, true);
req.setRequestHeader('Client-ID', clientID);
req.setRequestHeader('Accept', 'application/vnd.twitchtv.v5+json');
req.send();
req.onload = function () {
if (req.status >= 200 && req.status < 400) {
let data
try {
data = JSON.parse(req.response)
} catch (err) {
showError();
return;
}
callback(data)
} else {
showError();
}
}
}
const navList = document.querySelector('.nav__list')
const streamBox = document.querySelector('.stream_box')
const streamItems = document.querySelector('.streamItems')
const langFilter = document.querySelector('.langFilter')
const langArr = ['ALL', 'EN', 'ZH', 'ES', 'FR', 'DE', 'RU', 'KO', 'JA', 'PT', 'AR'];
const urlRoot = 'https://api.twitch.tv/kraken/'
const topGameurl = `${urlRoot}games/top?limit=5`
const streamApi = `${urlRoot}streams/`
let offset = 0
document.addEventListener('DOMContentLoaded', ()=> {
const target = document.querySelector('.stream-end')
let options = {
root: null,
rootMargin: '30px', // looking entire viewport
threshold: 0.5, // if 50% of footer
}
const observer = new IntersectionObserver(handleIntersection, options)
observer.observe(target)
})
function handleIntersection(entries) {
if (entries[0].isIntersecting) {
let gameTitle = document.querySelector('.gameTitle')
let gameURLname = encodeURIComponent(gameTitle.innerHTML)
const loadmorestreamUrl = createURL(streamApi, gameURLname, offset)
offset += 100
getData(loadmorestreamUrl)
}
}
function createURL(url, game, offset) {
const streamUrl = `${url}?game=${game}&limit=20&offset=${offset}`
return streamUrl
}
getResp(topGameurl, (data) => {
const topGames = [...data.top]
const result = topGames.reduce((result, item) => {
result += `<li>${item.game.name}</li>`
return result
}, '')
navList.innerHTML = result
const gameName = encodeURIComponent(data.top[0].game.name)
const streamUrl = createURL(streamApi, gameName, offset)
getData(streamUrl)
})
navList.addEventListener('click', e => {
streamItems.innerHTML = ''
let gameTitle = document.querySelector('.gameTitle')
const gameName = e.target.innerHTML
gameTitle.innerHTML = gameName
const gameNameURL = encodeURIComponent(gameName)
const streamUrl = createURL(streamApi, gameNameURL, offset)
getData(streamUrl)
})
function getData(url) {
getResp(url, (data) => {
const dataArrs = [...data.streams]
dataArrs.map(dataArr => {
let streamItem = document.createElement('div')
streamItem.classList.add('stream')
streamItem.innerHTML = `
<p class="viewers">Viewers: ${dataArr.viewers}</p>
<img src="${dataArr.preview.large}" alt="" class="preview">
<div class="streamer">
<img src="${dataArr.channel.logo}" alt="" class="logo">
<p class="name">${dataArr.channel.name}</p>
<p class="lang">${dataArr.channel.broadcaster_language.toUpperCase()}</p>
</div>
`
streamItems.appendChild(streamItem)
})
})
}
html, body {
font-size: 16px;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background:black;
background-attachment: fixed;
}
.container {
width: 90%;
margin: 0 auto;
display: flex;
flex-direction: column;
padding: 2rem;
overflow-x: hidden;
}
#header {
display: flex;
justify-content: space-between;
align-items: center;
color: #fff;
margin-bottom: 1.2rem;
}
.navbar {
position: relative;
transform: translateX(0%);
}
.title {
margin-right: 1rem;
font-size: 2rem;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
font-weight: 700;
color: #741cf7;
}
.nav__list {
display: flex;
}
.nav__list li {
padding: .8rem .6rem;
cursor: pointer;
font-size: 1rem;
font-weight: 700;
}
.nav__list li:hover {
border-radius: .5rem;
background: #a87ceb;
transition: background .3s ease;
}
.nav__list li + li {
margin-left: 1rem;
}
#stream_box {
display: flex;
flex-direction: column;
border-radius: 8px;
align-items: center;
padding: 1.5rem;
color: #fff;
position: relative;
}
.game__title {
font-size: 2rem;
margin-bottom: 1rem;
font-weight: 600;
color:#741cf7;
}
.top__twenty {
font-size: 1.4rem;
margin-bottom: 1.5rem;
}
.streamItems {
display: flex;
flex-flow: row wrap;
width: 100%;
justify-content: center;
}
.lang__options {
position: absolute;
top: 2rem;
right: 7rem;
}
.lang__title {
font-size: 1.2rem;
color: #fff;
}
#language {
outline: none;
width: 3rem;
background: transparent;
color: #741cf7;
border: none;
font-size: 1rem;
}
.stream {
width: 30%;
margin: 1.5rem;
background-color: rgba(255, 255, 255, .15);
backdrop-filter: blur(5px);
cursor: pointer;
}
.preview {
width: 100%;
vertical-align: middle;
}
.viewers {
display: none;
}
.stream:hover > .viewers {
display: block;
position: absolute;
z-index: -1;
animation-name: move;
animation-duration: .4s;
animation-timing-function: ease;
animation-fill-mode: forwards;
}
#keyframes move {
from {
top: 0px;
}
to {
top: -20px;
}
}
.streamer {
display: flex;
align-items: center;
padding: .5rem;
position: relative;
color: #fff;
}
.logo {
width: 15%;
border-radius: 50%;
margin-right: .8rem;
}
.lang {
position: absolute;
right: .5rem;
bottom: .5rem;
}
.hidden {
display: none;
}
.check {
display: none;
}
.rwdSwitch {
display: none;
}
.gameTitle {
color: #fff;
}
#media screen and (max-width: 1024px) {
.lang__options {
position: absolute;
top: 7rem;
right: 50%;
transform: translateX(50%);
}
.streamItems {
flex-flow: row wrap;
width: 100%;
justify-content: center;
margin-top: 1.2rem;
}
.stream {
width: 100%;
}
.nav__list li {
text-align: center;
font-size: 1rem;
}
}
#media screen and (max-width: 576px) {
body {
overflow-x: hidden;
}
.navbar {
position: absolute;
top: 20%;
right: 50%;
transform: translateX(200%);
transition: transform .3s ease-in-out;
background-color: rgba(255, 255, 255, .15);
backdrop-filter: blur(5px);
z-index: 999;
visibility: hidden;
}
.top__twenty {
font-size: .8rem;
}
.nav__list {
flex-direction: column;
align-items: flex-end;
}
.rwdSwitch {
display: block;
cursor: pointer;
position: absolute;
right:1.5rem;
top: 2.5rem;
z-index: 999;
}
.check:checked ~ .navbar {
visibility: visible;
transform: translateX(100%);
transition: transform .3s ease-in-out;
}
}
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="reset.css">
<link rel="stylesheet" href="style.css">
<title>Twitch</title>
</head>
<body>
<div class="container">
<header id="header">
<label for="check__status" class="rwdSwitch"><i class="fas fa-bars"></i></label>
<input type="checkbox" class="check" id="check__status">
<h1 class="title">Twitch Top Games</h1>
<nav class="navbar">
<ul class="nav__list"></ul>
</nav>
</header>
<div class="selections">
<label for="language" class="lang__title">Filter by Language: </label>
<select name="lang" id="language" class="langFilter"></select>
</div>
<main class="stream_box">
<p class="gameTitle"></p>
<div class="streamItems"></div>
</main>
<div class="stream-end"></div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.14.0/js/all.min.js"></script>
<script src="api.js"></script>
<script src="app.js"></script>
</body>
</html>
First of all please don't use XMLHttpRequest it's very old and hard to use and read. Use fetch instead.
Your problem is caused by the fact that you are creating one XMLHttpRequest. So before the first request is completed the second request starts and cancel the first one. Just move const req = new XMLHttpRequest(); to getResp.
But I would just change getResp to:
function getResp(url, callback) {
fetch(url, {
headers: {
'Client-ID': clientID,
'Accept': 'application/vnd.twitchtv.v5+json'
}
})
.then(response => response.json())
.then(callback)
.catch(showError)
}
I've built a search bar dropdown as part of a navbar I'm working on in React. The search bar's starting position is above the window, top: -112px. When the search button on the nav is clicked, the search bar animation begins and the the drop down goes from top: -112px to top: 0. After the animation is complete and the 'shown' class is applied to the search bar container, a useEffect watching the displayClass value triggers and focuses on the search bar input field.
This works fine on desktop Chrome and Safari, but on mobile the input field does not focus once in view for either browser. I've tried setTimeouts of every length on the focus call to no avail. Any help would be greatly appreciated.
SearchBar component:
export const SearchBar = ({
analyticsSearchBarSearchInput,
display,
history,
onClose,
}) => {
const [displayClass, setDisplayClass] = useState("");
const [searchActive, setSearchActive] = useState(false);
const prevDisplayRef = useRef();
useEffect(() => {
prevDisplayRef.current = display;
});
const prevDisplay = prevDisplayRef.current;
useEffect(() => {
if (!prevDisplay && display) {
onSearchOpen();
} else if (prevDisplay && !display) {
onSearchClose();
}
}, [display, prevDisplay]);
useEffect(() => {
if (displayClass === "shown") {
document.getElementById("search-bar-input").focus({
preventScroll: true,
});
}
}, [displayClass]);
return (
<ConditionalWrapper
condition={displayClass === "shown"}
wrapper={(children) => (
<OutsideClick onClick={onClose}>{children}</OutsideClick>
)}
>
<div className={`search-bar__container ${displayClass}`}>
<div className="search-bar__left">
<div className={`search-bar__search-icon${getSearchIconClass()}`} />
<div className="search-bar__input">
<SearchForm
onSubmit={onSearchSubmit}
searchActive={searchActive}
setSearchActive={setSearchActive}
/>
</div>
</div>
<div className="search-bar__right">
<div
aria-label="Close search"
className="search-bar__close-icon"
onClick={onClose}
onKeyDown={onClose}
role="button"
tabIndex={0}
/>
</div>
</div>
</ConditionalWrapper>
);
function getSearchIconClass() {
return searchActive ? " active" : "";
}
function onSearchClose() {
setDisplayClass("hide");
setTimeout(() => {
setDisplayClass("");
}, 300);
enableBodyScroll();
document.body.removeEventListener("touchmove", touchMoveCallback, {
passive: false,
});
}
function onSearchOpen() {
setDisplayClass("show");
setTimeout(() => {
setDisplayClass("shown");
}, 200);
disableBodyScroll();
document.body.addEventListener("touchmove", touchMoveCallback, {
passive: false,
});
}
function onSearchSubmit(usersInput) {
const hostUrl = window.location.host;
const pageNumber = 1;
const searchUrl = `${hostUrl}/search?query=${usersInput}&page=${pageNumber}`;
if (displayClass === "shown" && usersInput !== "") {
Promise.resolve(
analyticsSearchBarSearchInput(hostUrl, usersInput, searchUrl)
).then(() => {
onClose();
history.push(`/search?query=${usersInput}&page=${pageNumber}`);
});
}
}
};
const ConditionalWrapper = ({ condition, wrapper, children }) =>
condition ? wrapper(children) : children;
const touchMoveCallback = (e) => {
e.preventDefault();
};
SearchBar Form Component:
export const SearchForm = ({ onSubmit, searchActive, setSearchActive }) => {
const [search, setSearch] = useState("");
useEffect(() => {
search !== "" ? setSearchActive(true) : setSearchActive(false);
}, [search, setSearchActive]);
return (
<div className="search-bar__form-container">
<form action="#" onSubmit={onSearchSubmit} className="search-bar__form">
<input
autoComplete="off"
name="search"
className="search-bar__input"
id="search-bar-input"
onChange={handleChange}
placeholder="Search"
type="search"
value={search}
onKeyDown={onKeyDown}
/>
</form>
{searchActive && (
<div
aria-label="Search submit"
className="search-bar__submit-button"
onClick={onSearchSubmit}
onKeyDown={onKeyDown}
role="button"
tabIndex={0}
>
SEARCH>
</div>
)}
</div>
);
function handleChange(event) {
setSearch(event.target.value);
}
function onSearchSubmit() {
onSubmit(search);
setSearch("");
document.activeElement.blur();
}
function onKeyDown(event) {
if (event.keyCode === 13) {
event.preventDefault();
onSearchSubmit();
}
}
};
Styling:
.search-bar {
&__close-icon {
background: url("/images/icons/close.png") no-repeat;
background-size: contain;
cursor: pointer;
height: 26px;
transition: all 80ms ease-out;
width: 26px;
&:hover {
background: url("/images/icons/close-hover.png") no-repeat;
background-size: contain;
}
}
&__container {
align-items: center;
background-color: #ffffff;
border-bottom: 1px solid #d8d8d8;
color: #c5c5c5;
display: flex;
height: 112px;
justify-content: space-between;
left: 0;
position: fixed;
top: -112px;
width: 100%;
z-index: 1020;
&.show {
animation: searchFadeIn 200ms ease-out;
}
&.shown {
top: 0;
box-shadow: 0 0 0 100vmax rgba(51, 51, 51, 0.4);
}
&.hide {
animation: searchFadeOut 300ms ease-in;
}
}
#keyframes searchFadeIn {
0% {
box-shadow: 0 0 0 100vmax rgba(51, 51, 51, 0);
}
50% {
box-shadow: 0 0 0 100vmax rgba(51, 51, 51, 0.2);
}
100% {
box-shadow: 0 0 0 100vmax rgba(51, 51, 51, 0.4);
top: 0px;
}
}
#keyframes searchFadeOut {
0% {
box-shadow: 0 0 0 100vmax rgba(51, 51, 51, 0.4);
top: 0;
}
50% {
box-shadow: 0 0 0 100vmax rgba(51, 51, 51, 0.2);
}
100% {
box-shadow: 0 0 0 100vmax rgba(51, 51, 51, 0);
top: -75px;
}
}
&__icon {
background: url("/images/icons/search.png") no-repeat;
background-size: contain;
cursor: pointer;
height: 24px;
padding-right: 20px;
width: 24px;
}
&__input {
background: none;
border: none;
color: #333333;
flex: 1;
font-family: Setimo;
font-size: 42px;
line-height: 1.24;
letter-spacing: 2px;
max-width: 321px;
padding-right: 10px;
}
&__form {
margin: 0 20px 0 20px;
}
&__form-container {
display: flex;
}
&__left {
align-items: center;
display: flex;
padding-left: 40px;
}
&__right {
color: inherit;
font-size: 30px;
padding-right: 40px;
}
&__search-icon {
background: url("/images/icons/search-grey.png") no-repeat;
background-size: contain;
cursor: pointer;
height: 24px;
padding-right: 20px;
width: 24px;
&.active {
background: url("/images/icons/search.png") no-repeat;
background-size: contain;
}
}
&__submit-button {
align-self: flex-end;
color: #9a9a9a;
cursor: pointer;
flex: 1;
font-family: Setimo;
font-size: 11px;
font-weight: bold;
line-height: 1.64;
letter-spacing: 3.5px;
margin-bottom: 7px;
transition: all 80ms ease-out;
width: 81px;
&:hover {
color: #333333;
}
}
}
.search-icon {
&__container {
align-items: center;
cursor: pointer;
display: flex;
justify-content: space-between;
transition: all 80ms ease-out;
width: 97px;
&:hover .search-icon__text {
color: #7d7be4;
}
&:hover .search-icon__icon {
background: url("/images/icons/search-hover.png") no-repeat;
background-size: contain;
}
}
&__icon {
background: url("/images/icons/search.png") no-repeat;
background-size: contain;
cursor: pointer;
height: 20px;
width: 20px;
}
&__text {
color: #333333;
font-family: Setimo;
font-size: 11px;
font-weight: bold;
letter-spacing: 3.5px;
line-height: 1.64;
}
}
::placeholder {
color: #c5c5c5;
}
[type="search"] {
-webkit-appearance: textfield;
}
input[type="text"]::-ms-clear {
display: none;
width: 0;
height: 0;
}
input[type="text"]::-ms-reveal {
display: none;
width: 0;
height: 0;
}
input[type="search"]::-webkit-search-decoration,
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-results-button,
input[type="search"]::-webkit-search-results-decoration {
display: none;
}
#media (max-width: 959px) {
.search-bar {
&__container {
height: 80px;
top: -80px;
}
&__form {
margin: 0;
}
&__input {
font-size: 32px;
max-width: 225px;
}
&__left {
padding-left: 30px;
}
&__right {
padding-right: 30px;
}
&__search-icon {
padding-right: 14px;
}
}
}
#media (max-width: 480px) {
.search-bar {
&__close-icon {
height: 20px;
width: 20px;
}
&__container {
height: 64px;
top: -64px;
}
&__form {
margin: 0;
}
&__input {
font-size: 24px;
letter-spacing: normal;
max-width: 175px;
}
&__left {
padding-left: 16px;
}
&__right {
padding-right: 16px;
}
&__search-icon {
padding-right: 10px;
}
&__submit-button {
display: none;
}
}
}