Welcome!

By registering with us, you'll be able to discuss, share and private message with other members of our community.

SignUp Now!
  • Guest, before posting your code please take these rules into consideration:
    • It is required to use our BBCode feature to display your code. While within the editor click < / > or >_ and place your code within the BB Code prompt. This helps others with finding a solution by making it easier to read and easier to copy.
    • You can also use markdown to share your code. When using markdown your code will be automatically converted to BBCode. For help with markdown check out the markdown guide.
    • Don't share a wall of code. All we want is the problem area, the code related to your issue.


    To learn more about how to use our BBCode feature, please click here.

    Thank you, Code Forum.

JavaScript How to animate input placeholder text shaking in React using Framer Motion

accol

Coder
Thank you for reading. I'm trying to figure out how to make each letter of the placeholder username and password text either shake or animate in a wavy motion, however if you type in words the placeholder would disappear and stop animating as typical. I was using Framer Motion & React: Wavy Letter/Text Animation as an example but couldn't figure out how to adapt it to my situation.

Due to input is a void element tag and must neither have children nor use dangerouslySetInnerHTML, I'm not entirely sure how to create the animation then. My thought was to create another functional component to handle the placeholder text animation (there is motion.placeholder so I was thinking of using that) then passing it in as the value to the placeholder attribute but I don't think that would work because it takes in string values.

1. Am I missing something obvious am not using motion.input correctly?
2. Should I not be using Framer Motion and do something else like animating via CSS to achieve this effect?
3. Or is it not possible to achieve this effect via using input itself and I have to do something else to give the same illusion.

1708272942603.png
JSX:
const AuthenticationInput = ({ value, placeholder, onChange, className, isPassword = false }) => {
    /*code here to handle animation - hasn't been written*/
    return (
        <motion.input
            value={value}
            placeholder={placeholder}
            onChange={onChange}
            className={className}
            type={(isPassword) ? "password" : "text"}
        />
    );
}
const Authentication = (props) => {
    /*code*/
    return <div className={"auth-container"}>
        <div className={"auth-branding-container"}>
            <h1>OMORI</h1>
        </div>
        <Tabs defaultActiveKey="login" variant="tabs" justify>
            <Tab eventKey="login" title="LOGIN">
                <div className={"auth-form-container"}>
                    <div className={"auth-input-container"}>
                        <AuthenticationInput value={username} placeholder="WHAT IS YOUR NAME" onChange={ev => setUsername(ev.target.value)} className={"auth-input-box"} />
                        <label className="auth-error-label">{usernameError}</label>
                    </div>
                    <div className={"auth-input-container"}>
                        <AuthenticationInput value={password} placeholder="PASSWORD, PLEASE." onChange={ev => setPassword(ev.target.value)} className={"auth-input-box"} isPassword={true} />
                        <label className="auth-error-label">{passwordError}</label>
                    </div>
                </div>
                <div className={"auth-actions-container"}>
                    <AuthenticationButton value={"LOG IN"} onClick={onLoginButtonClick} className={"auth-btn"} />
                </div>
            </Tab>
            <Tab eventKey="register" title="REGISTER">
                <div className={"auth-form-container"}>
                    <div className={"auth-input-container"}>
                        <AuthenticationInput value={username} placeholder="WHAT IS YOUR NAME" onChange={ev => setUsername(ev.target.value)} className={"auth-input-box"} />
                        <label className="auth-error-label">{usernameError}</label>
                    </div>
                    <div className={"auth-input-container"}>
                        <AuthenticationInput value={password} placeholder="PASSWORD, PLEASE." onChange={ev => setPassword(ev.target.value)} className={"auth-input-box"} isPassword={true} />
                        <label className="auth-error-label">{passwordError}</label>
                    </div>
                </div>
                <div className={"auth-actions-container"}>
                    <AuthenticationButton value={"REGISTER"} onClick={onRegisterButtonClick} className={"auth-btn"}/>
                </div>
            </Tab>
        </Tabs>
    </div>
}
 
Solution
Thank you for reading. I'm trying to figure out how to make each letter of the placeholder username and password text either shake or animate in a wavy motion, however if you type in words the placeholder would disappear and stop animating as typical. I was using Framer Motion & React: Wavy Letter/Text Animation as an example but couldn't figure out how to adapt it to my situation.

Due to input is a void element tag and must neither have children nor use dangerouslySetInnerHTML, I'm not entirely sure how to create the animation then. My thought was to create another functional component to handle the placeholder text animation (there is motion.placeholder so I was thinking of using that) then passing...
Thank you for reading. I'm trying to figure out how to make each letter of the placeholder username and password text either shake or animate in a wavy motion, however if you type in words the placeholder would disappear and stop animating as typical. I was using Framer Motion & React: Wavy Letter/Text Animation as an example but couldn't figure out how to adapt it to my situation.

Due to input is a void element tag and must neither have children nor use dangerouslySetInnerHTML, I'm not entirely sure how to create the animation then. My thought was to create another functional component to handle the placeholder text animation (there is motion.placeholder so I was thinking of using that) then passing it in as the value to the placeholder attribute but I don't think that would work because it takes in string values.

1. Am I missing something obvious am not using motion.input correctly?
2. Should I not be using Framer Motion and do something else like animating via CSS to achieve this effect?
3. Or is it not possible to achieve this effect via using input itself and I have to do something else to give the same illusion.

View attachment 2519
JSX:
const AuthenticationInput = ({ value, placeholder, onChange, className, isPassword = false }) => {
    /*code here to handle animation - hasn't been written*/
    return (
        <motion.input
            value={value}
            placeholder={placeholder}
            onChange={onChange}
            className={className}
            type={(isPassword) ? "password" : "text"}
        />
    );
}
const Authentication = (props) => {
    /*code*/
    return <div className={"auth-container"}>
        <div className={"auth-branding-container"}>
            <h1>OMORI</h1>
        </div>
        <Tabs defaultActiveKey="login" variant="tabs" justify>
            <Tab eventKey="login" title="LOGIN">
                <div className={"auth-form-container"}>
                    <div className={"auth-input-container"}>
                        <AuthenticationInput value={username} placeholder="WHAT IS YOUR NAME" onChange={ev => setUsername(ev.target.value)} className={"auth-input-box"} />
                        <label className="auth-error-label">{usernameError}</label>
                    </div>
                    <div className={"auth-input-container"}>
                        <AuthenticationInput value={password} placeholder="PASSWORD, PLEASE." onChange={ev => setPassword(ev.target.value)} className={"auth-input-box"} isPassword={true} />
                        <label className="auth-error-label">{passwordError}</label>
                    </div>
                </div>
                <div className={"auth-actions-container"}>
                    <AuthenticationButton value={"LOG IN"} onClick={onLoginButtonClick} className={"auth-btn"} />
                </div>
            </Tab>
            <Tab eventKey="register" title="REGISTER">
                <div className={"auth-form-container"}>
                    <div className={"auth-input-container"}>
                        <AuthenticationInput value={username} placeholder="WHAT IS YOUR NAME" onChange={ev => setUsername(ev.target.value)} className={"auth-input-box"} />
                        <label className="auth-error-label">{usernameError}</label>
                    </div>
                    <div className={"auth-input-container"}>
                        <AuthenticationInput value={password} placeholder="PASSWORD, PLEASE." onChange={ev => setPassword(ev.target.value)} className={"auth-input-box"} isPassword={true} />
                        <label className="auth-error-label">{passwordError}</label>
                    </div>
                </div>
                <div className={"auth-actions-container"}>
                    <AuthenticationButton value={"REGISTER"} onClick={onRegisterButtonClick} className={"auth-btn"}/>
                </div>
            </Tab>
        </Tabs>
    </div>
}
You can just create a separate component for the animated placeholder text. You can use the motion.div component to create the animation for each letter of the placeholder text.
Here's an example of how you can create an AnimatedPlaceholder component:

JSX:
import React from 'react';
import { motion } from 'framer-motion';

const AnimatedPlaceholder = ({ text }) => {
const letters = text.split('');

const letterVariants = {
initial: { y: 0 },
animate: {
y: [0, -5, 0, 5, 0],
transition: {
repeat: Infinity,
duration: 0.5,
},
},
};

return (
<div>
{letters.map((letter, index) => (
<motion.span
key={index}
variants={letterVariants}
initial="initial"
animate="animate"
>
{letter}
</motion.span>
))}
</div>
);
};

export default AnimatedPlaceholder;


Now, you can use this AnimatedPlaceholder component within your AuthenticationInput component:

JSX:
import React from 'react';
import { motion } from 'framer-motion';
import AnimatedPlaceholder from './AnimatedPlaceholder';

const AuthenticationInput = ({
value,
placeholder,
onChange,
className,
isPassword = false,
}) => {
return (
<div className={className}>
<motion.input
value={value}
onChange={onChange}
type={isPassword ? 'password' : 'text'}
/>
<AnimatedPlaceholder text={placeholder} />
</div>
);
};

export default AuthenticationInput;


In this example, the AnimatedPlaceholder component takes the placeholder text, splits it into individual letters, and animates each letter independently. The animation is a simple up and down motion, but you can customize it according to your needs.
Make sure to include the necessary CSS styles to position the motion.input and AnimatedPlaceholder components correctly within the container.
This approach keeps the animation separate from the input element, as animating the actual placeholder text directly within the motion.input component can be challenging due to the limitations of styling placeholder text with Framer Motion.
 
Solution
You can just create a separate component for the animated placeholder text. You can use the motion.div component to create the animation for each letter of the placeholder text.
Here's an example of how you can create an AnimatedPlaceholder component:

JSX:
import React from 'react';
import { motion } from 'framer-motion';

const AnimatedPlaceholder = ({ text }) => {
const letters = text.split('');

const letterVariants = {
initial: { y: 0 },
animate: {
y: [0, -5, 0, 5, 0],
transition: {
repeat: Infinity,
duration: 0.5,
},
},
};

return (
<div>
{letters.map((letter, index) => (
<motion.span
key={index}
variants={letterVariants}
initial="initial"
animate="animate"
>
{letter}
</motion.span>
))}
</div>
);
};

export default AnimatedPlaceholder;


Now, you can use this AnimatedPlaceholder component within your AuthenticationInput component:

JSX:
import React from 'react';
import { motion } from 'framer-motion';
import AnimatedPlaceholder from './AnimatedPlaceholder';

const AuthenticationInput = ({
value,
placeholder,
onChange,
className,
isPassword = false,
}) => {
return (
<div className={className}>
<motion.input
value={value}
onChange={onChange}
type={isPassword ? 'password' : 'text'}
/>
<AnimatedPlaceholder text={placeholder} />
</div>
);
};

export default AuthenticationInput;


In this example, the AnimatedPlaceholder component takes the placeholder text, splits it into individual letters, and animates each letter independently. The animation is a simple up and down motion, but you can customize it according to your needs.
Make sure to include the necessary CSS styles to position the motion.input and AnimatedPlaceholder components correctly within the container.
This approach keeps the animation separate from the input element, as animating the actual placeholder text directly within the motion.input component can be challenging due to the limitations of styling placeholder text with Framer Motion.
Thank you for helping me, I really appreciate it. I've tried applying your implementation but slightly modified it. When I inspect the page it shows that the letters are changing position but it doesn't look like that on the actual page itself? I'm not sure as to the cause. I don't know if it's because I screwed up with my CSS somewhere.
video.gif
CSS:
.auth-input-section {
    position: relative;
}

.auth-input-box {
    height: 100%;
    width: 100%;
    border-radius: 8px;
}

.auth-placeholder-overlay {
    position: absolute;
    left: 1%;
    top: 20%;
    color: #888;
    pointer-events: none;
}
JSX:
/*Separate to possibly be used elsewhere, probably gonna rename some parts and animate the input*/
const AuthenticationInput = ({ value, placeholder, onChange, className, isPassword = false }) => {
    return (
        <motion.input
            value={value}
            placeholder={placeholder}
            onChange={onChange}
            className={className}
            type={(isPassword) ? "password" : "text"}
        />
    );
}

const AuthenticationPlaceholder = ({ value, placeholder }) => {
    const letters = Array.from(placeholder);

    return (
        <div className="auth-placeholder-overlay">
            {letters.map((letter, index) => (
                <motion.span
                    key={index}
                    animate={value.length <= 0 ? "visible" : "hidden"}
                    variants={
                        {
                            visible: {
                                y: [0, -5, 0, 5, 0],
                                transition: {
                                    repeat: Infinity,
                                    duration: 0.5,
                                },
                                delay: index * 0.1
                            },
                            hidden: {
                                opacity: 0,
                                transition: { duration: 0.25 }
                            }
                        }
                    }
                >
                    {letter}
                </motion.span>
            ))}
        </div>
    );
};

const AuthenticationInputSection = ({ value, placeholder, onChange, className, isPassword }) => {
    return (
        <div className="auth-input-section">
            <AuthenticationInput
                value={value}
                placeholder=""
                onChange={onChange}
                className={className}
                isPassword={isPassword}
            />
            <AuthenticationPlaceholder value={value} placeholder={ placeholder } />
        </div>
    );
};
 
Thank you for helping me, I really appreciate it. I've tried applying your implementation but slightly modified it. When I inspect the page it shows that the letters are changing position but it doesn't look like that on the actual page itself? I'm not sure as to the cause. I don't know if it's because I screwed up with my CSS somewhere.
View attachment 2521
CSS:
.auth-input-section {
    position: relative;
}

.auth-input-box {
    height: 100%;
    width: 100%;
    border-radius: 8px;
}

.auth-placeholder-overlay {
    position: absolute;
    left: 1%;
    top: 20%;
    color: #888;
    pointer-events: none;
}
JSX:
/*Separate to possibly be used elsewhere, probably gonna rename some parts and animate the input*/
const AuthenticationInput = ({ value, placeholder, onChange, className, isPassword = false }) => {
    return (
        <motion.input
            value={value}
            placeholder={placeholder}
            onChange={onChange}
            className={className}
            type={(isPassword) ? "password" : "text"}
        />
    );
}

const AuthenticationPlaceholder = ({ value, placeholder }) => {
    const letters = Array.from(placeholder);

    return (
        <div className="auth-placeholder-overlay">
            {letters.map((letter, index) => (
                <motion.span
                    key={index}
                    animate={value.length <= 0 ? "visible" : "hidden"}
                    variants={
                        {
                            visible: {
                                y: [0, -5, 0, 5, 0],
                                transition: {
                                    repeat: Infinity,
                                    duration: 0.5,
                                },
                                delay: index * 0.1
                            },
                            hidden: {
                                opacity: 0,
                                transition: { duration: 0.25 }
                            }
                        }
                    }
                >
                    {letter}
                </motion.span>
            ))}
        </div>
    );
};

const AuthenticationInputSection = ({ value, placeholder, onChange, className, isPassword }) => {
    return (
        <div className="auth-input-section">
            <AuthenticationInput
                value={value}
                placeholder=""
                onChange={onChange}
                className={className}
                isPassword={isPassword}
            />
            <AuthenticationPlaceholder value={value} placeholder={ placeholder } />
        </div>
    );
};
There might be an issue with the positioning and styling that prevents the animation from appearing correctly on the page. Here are a few suggestions to troubleshoot and improve your implementation:
  1. Adjust the Overlay Position:
    • Ensure that the left and top values for the .auth-placeholder-overlay are appropriate for your design. The left: 1%; might be causing the animation to be partially off-screen. Try adjusting the values to center it within the input.
  2. Check Z-Index:
    • If other elements are overlapping with the placeholder overlay, you might need to adjust the z-index property to make sure it appears above the input.
  3. Check Container Sizing:
    • Ensure that the .auth-input-section container has sufficient height to accommodate both the input and the placeholder overlay. If the height is too small, it might cut off the animation.
  4. Adjust Animation Delay:
    • The delay in the animation might be causing them to overlap or appear differently than expected. Experiment with the delay values and see if it makes a difference.
Here's a modified version of your CSS and JSX code to consider:

CSS:
.auth-input-section {
position: relative;
/* Adjust height as needed */
height: 50px;
}

.auth-input-box {
height: 100%;
width: 100%;
border-radius: 8px;
}

.auth-placeholder-overlay {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
color: #888;
pointer-events: none;
/* Add z-index if necessary */
z-index: 1;
}



JSX:
const AuthenticationInputSection = ({ value, placeholder, onChange, className, isPassword }) => {
return (
<div className="auth-input-section">
<AuthenticationInput
value={value}
placeholder=""
onChange={onChange}
className={className}
isPassword={isPassword}
/>
<AuthenticationPlaceholder value={value} placeholder={placeholder} />
</div>
);
}:
Adjust these values based on your specific design requirements and layout. If the issue persists, you may want to inspect the elements using your browser's developer tools to see if there are any CSS conflicts or layout issues that need to be addressed.
 
There might be an issue with the positioning and styling that prevents the animation from appearing correctly on the page. Here are a few suggestions to troubleshoot and improve your implementation:
  1. Adjust the Overlay Position:
    • Ensure that the left and top values for the .auth-placeholder-overlay are appropriate for your design. The left: 1%; might be causing the animation to be partially off-screen. Try adjusting the values to center it within the input.
  2. Check Z-Index:
    • If other elements are overlapping with the placeholder overlay, you might need to adjust the z-index property to make sure it appears above the input.
  3. Check Container Sizing:
    • Ensure that the .auth-input-section container has sufficient height to accommodate both the input and the placeholder overlay. If the height is too small, it might cut off the animation.
  4. Adjust Animation Delay:
    • The delay in the animation might be causing them to overlap or appear differently than expected. Experiment with the delay values and see if it makes a difference.
Here's a modified version of your CSS and JSX code to consider:

CSS:
.auth-input-section {
position: relative;
/* Adjust height as needed */
height: 50px;
}

.auth-input-box {
height: 100%;
width: 100%;
border-radius: 8px;
}

.auth-placeholder-overlay {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
color: #888;
pointer-events: none;
/* Add z-index if necessary */
z-index: 1;
}



JSX:
const AuthenticationInputSection = ({ value, placeholder, onChange, className, isPassword }) => {
return (
<div className="auth-input-section">
<AuthenticationInput
value={value}
placeholder=""
onChange={onChange}
className={className}
isPassword={isPassword}
/>
<AuthenticationPlaceholder value={value} placeholder={placeholder} />
</div>
);
}:
Adjust these values based on your specific design requirements and layout. If the issue persists, you may want to inspect the elements using your browser's developer tools to see if there are any CSS conflicts or layout issues that need to be addressed.

So I've modified the code to take out redundancies and renamed things again.
CSS:
@font-face {
    font-family: 'OMORI';
    src: url('../assets/fonts/OMORI_GAME.ttf') format('truetype'), url('../assets/fonts/OMORI_GAME2.ttf') format('truetype');
}

body {
    font-family: 'OMORI';
    margin: 0;
    align-items: center;
    justify-content: center;
    font-size: 30px;
}

.auth-container {
    width: 30%;
    height: 50%;
    border: 1px solid #ccc;
    border-radius: 8px;
    background-color: #f9f9f9;
    backdrop-filter: drop-shadow();
    padding: 1%;
    flex-direction: column;
    justify-content: center;
    margin: 0 auto;
}

.auth-branding-container {
    /*border: 1px solid grey;*/
    margin: 0% 0% 2.5% 0%;
    display: flex;
    justify-content: inherit;
}

.auth-form-container {
    margin: 5%;
}

/*Removed original .auth-input-container and made .auth-input-section into it instead*/
.auth-input-container {
    padding: 1%;
    position: relative;
}

/*Originally .auth-input-box*/
.auth-input {
    height: 100%;
    width: 100%;
    border-radius: 8px;
}

.auth-placeholder-overlay {
    position: absolute;
    left: 2.2%;
    top: 4%;
    color: #888;
    pointer-events: none;
}

.auth-error-label {
    font-size: 70%;
    color: darkred;
    display: block;
}




.auth-actions-container {
    display: flex;
    justify-content: center;
}

.auth-btn {
    border-radius: 8px;
    border: 1px solid transparent;
    padding: 0.6em 1.2em;
    font-size: 100%;
    font-weight: 700;
    cursor: pointer;
    transition: border-color 0.25s;
}

.auth-btn:hover {
    border-color: #646cff;
}

.auth-btn:focus,
.auth-btn:focus-visible {
    outline: 4px auto -webkit-focus-ring-color;
}
JSX:
import React, { useState } from "react";
// import { useNavigate } from "react-router-dom";

import 'bootstrap/dist/css/bootstrap.min.css';

import Tab from 'react-bootstrap/Tab';
import Tabs from 'react-bootstrap/Tabs';

import "./authentication.css";

import { motion } from "framer-motion";

import OMORISpriteRun from '../assets/sprites/OMORI_Sprite_run.gif';

const AuthenticationInput = ({ value, placeholder, onChange, type="text" }) => {
    return (
        <motion.input
            value={value}
            placeholder={placeholder}
            onChange={onChange}
            className={"auth-input"}
            type={type}
        />
    );
}

const AuthenticationPlaceholder = ({ value, placeholder }) => {
    const letters = Array.from(placeholder);

    return (
        <div className="auth-placeholder-overlay">
            {letters.map((letter, index) => (
                <motion.span
                    key={index}
                    animate={value.length <= 0 ? "visible" : "hidden"}
                    variants={
                        {
                            visible: {
                                y: [0, -5, 0, 5, 0],
                                transition: {
                                    repeat: Infinity,
                                    duration: 0.5,
                                },
                                delay: index * 0.1
                            },
                            hidden: {
                                opacity: 0,
                                transition: { duration: 0.25 }
                            }
                        }
                    }
                >
                    {letter}
                </motion.span>
            ))}
        </div>
    );
};

const AuthenticationInputContainer = ({ value, placeholder, onChange, className, type ="text", error }) => {
    return (
        <div className="auth-input-container">
            <AuthenticationInput
                value={value}
                placeholder=""
                onChange={onChange}
                className={className}
                type={type}
            />
            <AuthenticationPlaceholder value={value} placeholder={placeholder} />
            <label className="auth-error-label">{error}</label>
        </div>
    );
};

const AuthenticationButton = ({ value, onClick, className }) => {
    return (
        <motion.button
            onClick={ onClick }
            className={ className }
            whileHover = {{ scale: 1.1 }}
            whileTap = {{ scale: 0.9 }}
        >{ value }</motion.button>
    );
}

const Authentication = (props) => {
    const [username, setUsername] = useState("")
    const [password, setPassword] = useState("")
    const [usernameError, setUsernameError] = useState("")
    const [passwordError, setPasswordError] = useState("")

    // const navigate = useNavigate();

    const onRegisterButtonClick = () => {
        if (!onCheckValidInput()) {
            return;
        }

        alert('On register button click');
    }

    const onLoginButtonClick = () => {
        if (!onCheckValidInput()) {
            return;
        }

        alert('On login button click');
    }

    const onCheckValidInput = () => {
        // Set initial error values to empty
        setUsernameError('')
        setPasswordError('')

        // Check if the user has entered both fields correctly
        if ('' === username) {
            setUsernameError('Please enter your username.')
            return false;
        }

        if ('' === password) {
            setPasswordError('Please enter a password.')
            return false;
        }

        const password_requirement = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+])[A-Za-z\d!@#$%^&*()_+]{8,}$";

        if (!password.match(password_requirement)) {
            setPasswordError('8 characters minimum; at least 1 uppercase, lowercase, number, and special character.')
            return false;
        }

        return true;
    }

    return <div className={"auth-container"}>
        <div className={"auth-branding-container"}>
            <img src={OMORISpriteRun} />
        </div>
        <Tabs defaultActiveKey="login" variant="tabs" justify>
            <Tab eventKey="login" title="LOGIN">
                <div className={"auth-form-container"}>
                    <AuthenticationInputContainer value={username} placeholder="WHAT IS YOUR NAME?" onChange={ev => setUsername(ev.target.value)} error={usernameError} />                    
                    <AuthenticationInputContainer value={password} placeholder="PASSWORD, PLEASE." onChange={ev => setPassword(ev.target.value)} type={"password"} error={passwordError} />
                </div>
                <div className={"auth-actions-container"}>
                    <AuthenticationButton value={"LOG IN"} onClick={onLoginButtonClick} className={"auth-btn"} />
                </div>
            </Tab>
            <Tab eventKey="register" title="REGISTER">
                <div className={"auth-form-container"}>
                    <AuthenticationInputContainer value={username} placeholder="WHAT IS YOUR NAME?" onChange={ev => setUsername(ev.target.value)} error={usernameError} />
                    <AuthenticationInputContainer value={password} placeholder="PASSWORD, PLEASE." onChange={ev => setPassword(ev.target.value)} type={"password"} error={passwordError}/>
                </div>
                <div className={"auth-actions-container"}>
                    <AuthenticationButton value={"REGISTER"} onClick={onRegisterButtonClick} className={"auth-btn"}/>
                </div>
            </Tab>
        </Tabs>
    </div>
}

export default Authentication;
  1. Adjust the Overlay Position:
    • Ensure that the left and top values for the .auth-placeholder-overlay are appropriate for your design. The left: 1%; might be causing the animation to be partially off-screen. Try adjusting the values to center it within the input.
  2. Check Z-Index:
    • If other elements are overlapping with the placeholder overlay, you might need to adjust the z-index property to make sure it appears above the input.
  3. Check Container Sizing:
    • Ensure that the .auth-input-section container has sufficient height to accommodate both the input and the placeholder overlay. If the height is too small, it might cut off the animation.
  4. Adjust Animation Delay:
    • The delay in the animation might be causing them to overlap or appear differently than expected. Experiment with the delay values and see if it makes a difference.
I tried playing around with the overlay position, added a z-index just to see it if it was an issue. I set .auth-input-section's height to be 100% as a test. I also just tried taking out delay to see if it would do anything and it didn't. The letters for the placeholder text still don't look like they're moving even though inspecting it shows they are?

I feel like I'm missing something obvious and am pretty bad at css so my apologies. I wouldn't be surprised if it does have to do with the container and it properly not wrapping around it?

I also tried two different browsers because I know sometimes things don't work due to browser. I'm not entirely sure how to wrap the input, placeholder overlay, and label correctly, or if I even am.
 
So I've modified the code to take out redundancies and renamed things again.
CSS:
@font-face {
    font-family: 'OMORI';
    src: url('../assets/fonts/OMORI_GAME.ttf') format('truetype'), url('../assets/fonts/OMORI_GAME2.ttf') format('truetype');
}

body {
    font-family: 'OMORI';
    margin: 0;
    align-items: center;
    justify-content: center;
    font-size: 30px;
}

.auth-container {
    width: 30%;
    height: 50%;
    border: 1px solid #ccc;
    border-radius: 8px;
    background-color: #f9f9f9;
    backdrop-filter: drop-shadow();
    padding: 1%;
    flex-direction: column;
    justify-content: center;
    margin: 0 auto;
}

.auth-branding-container {
    /*border: 1px solid grey;*/
    margin: 0% 0% 2.5% 0%;
    display: flex;
    justify-content: inherit;
}

.auth-form-container {
    margin: 5%;
}

/*Removed original .auth-input-container and made .auth-input-section into it instead*/
.auth-input-container {
    padding: 1%;
    position: relative;
}

/*Originally .auth-input-box*/
.auth-input {
    height: 100%;
    width: 100%;
    border-radius: 8px;
}

.auth-placeholder-overlay {
    position: absolute;
    left: 2.2%;
    top: 4%;
    color: #888;
    pointer-events: none;
}

.auth-error-label {
    font-size: 70%;
    color: darkred;
    display: block;
}




.auth-actions-container {
    display: flex;
    justify-content: center;
}

.auth-btn {
    border-radius: 8px;
    border: 1px solid transparent;
    padding: 0.6em 1.2em;
    font-size: 100%;
    font-weight: 700;
    cursor: pointer;
    transition: border-color 0.25s;
}

.auth-btn:hover {
    border-color: #646cff;
}

.auth-btn:focus,
.auth-btn:focus-visible {
    outline: 4px auto -webkit-focus-ring-color;
}
JSX:
import React, { useState } from "react";
// import { useNavigate } from "react-router-dom";

import 'bootstrap/dist/css/bootstrap.min.css';

import Tab from 'react-bootstrap/Tab';
import Tabs from 'react-bootstrap/Tabs';

import "./authentication.css";

import { motion } from "framer-motion";

import OMORISpriteRun from '../assets/sprites/OMORI_Sprite_run.gif';

const AuthenticationInput = ({ value, placeholder, onChange, type="text" }) => {
    return (
        <motion.input
            value={value}
            placeholder={placeholder}
            onChange={onChange}
            className={"auth-input"}
            type={type}
        />
    );
}

const AuthenticationPlaceholder = ({ value, placeholder }) => {
    const letters = Array.from(placeholder);

    return (
        <div className="auth-placeholder-overlay">
            {letters.map((letter, index) => (
                <motion.span
                    key={index}
                    animate={value.length <= 0 ? "visible" : "hidden"}
                    variants={
                        {
                            visible: {
                                y: [0, -5, 0, 5, 0],
                                transition: {
                                    repeat: Infinity,
                                    duration: 0.5,
                                },
                                delay: index * 0.1
                            },
                            hidden: {
                                opacity: 0,
                                transition: { duration: 0.25 }
                            }
                        }
                    }
                >
                    {letter}
                </motion.span>
            ))}
        </div>
    );
};

const AuthenticationInputContainer = ({ value, placeholder, onChange, className, type ="text", error }) => {
    return (
        <div className="auth-input-container">
            <AuthenticationInput
                value={value}
                placeholder=""
                onChange={onChange}
                className={className}
                type={type}
            />
            <AuthenticationPlaceholder value={value} placeholder={placeholder} />
            <label className="auth-error-label">{error}</label>
        </div>
    );
};

const AuthenticationButton = ({ value, onClick, className }) => {
    return (
        <motion.button
            onClick={ onClick }
            className={ className }
            whileHover = {{ scale: 1.1 }}
            whileTap = {{ scale: 0.9 }}
        >{ value }</motion.button>
    );
}

const Authentication = (props) => {
    const [username, setUsername] = useState("")
    const [password, setPassword] = useState("")
    const [usernameError, setUsernameError] = useState("")
    const [passwordError, setPasswordError] = useState("")

    // const navigate = useNavigate();

    const onRegisterButtonClick = () => {
        if (!onCheckValidInput()) {
            return;
        }

        alert('On register button click');
    }

    const onLoginButtonClick = () => {
        if (!onCheckValidInput()) {
            return;
        }

        alert('On login button click');
    }

    const onCheckValidInput = () => {
        // Set initial error values to empty
        setUsernameError('')
        setPasswordError('')

        // Check if the user has entered both fields correctly
        if ('' === username) {
            setUsernameError('Please enter your username.')
            return false;
        }

        if ('' === password) {
            setPasswordError('Please enter a password.')
            return false;
        }

        const password_requirement = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+])[A-Za-z\d!@#$%^&*()_+]{8,}$";

        if (!password.match(password_requirement)) {
            setPasswordError('8 characters minimum; at least 1 uppercase, lowercase, number, and special character.')
            return false;
        }

        return true;
    }

    return <div className={"auth-container"}>
        <div className={"auth-branding-container"}>
            <img src={OMORISpriteRun} />
        </div>
        <Tabs defaultActiveKey="login" variant="tabs" justify>
            <Tab eventKey="login" title="LOGIN">
                <div className={"auth-form-container"}>
                    <AuthenticationInputContainer value={username} placeholder="WHAT IS YOUR NAME?" onChange={ev => setUsername(ev.target.value)} error={usernameError} />                   
                    <AuthenticationInputContainer value={password} placeholder="PASSWORD, PLEASE." onChange={ev => setPassword(ev.target.value)} type={"password"} error={passwordError} />
                </div>
                <div className={"auth-actions-container"}>
                    <AuthenticationButton value={"LOG IN"} onClick={onLoginButtonClick} className={"auth-btn"} />
                </div>
            </Tab>
            <Tab eventKey="register" title="REGISTER">
                <div className={"auth-form-container"}>
                    <AuthenticationInputContainer value={username} placeholder="WHAT IS YOUR NAME?" onChange={ev => setUsername(ev.target.value)} error={usernameError} />
                    <AuthenticationInputContainer value={password} placeholder="PASSWORD, PLEASE." onChange={ev => setPassword(ev.target.value)} type={"password"} error={passwordError}/>
                </div>
                <div className={"auth-actions-container"}>
                    <AuthenticationButton value={"REGISTER"} onClick={onRegisterButtonClick} className={"auth-btn"}/>
                </div>
            </Tab>
        </Tabs>
    </div>
}

export default Authentication;

I tried playing around with the overlay position, added a z-index just to see it if it was an issue. I set .auth-input-section's height to be 100% as a test. I also just tried taking out delay to see if it would do anything and it didn't. The letters for the placeholder text still don't look like they're moving even though inspecting it shows they are?

I feel like I'm missing something obvious and am pretty bad at css so my apologies. I wouldn't be surprised if it does have to do with the container and it properly not wrapping around it?

I also tried two different browsers because I know sometimes things don't work due to browser. I'm not entirely sure how to wrap the input, placeholder overlay, and label correctly, or if I even am.

1. Overlay Position:​

Adjust the position of the .auth-placeholder-overlay to be relative to the .auth-input-container. You can use percentage values or other units to ensure the overlay is positioned correctly within its parent container.

CSS:
.auth-input-container {
position: relative;
}

.auth-placeholder-overlay {
position: absolute;
left: 2%;  /* Adjust this value based on your design */
top: 50%;  /* Adjust this value based on your design */
transform: translateY(-50%);
color: #888;
pointer-events: none;
}

2. Adjust the Animation:​

To make the animation more noticeable, you can increase the range of motion. For example, change the y values in the animation to a larger range.

JSX:
y: [0, -10, 0, 10, 0],

3. Verify Animation State:​

Ensure that the animation state is properly toggling between "visible" and "hidden." You might log the animation state to the console or visually inspect it using React DevTools.

JSX:
<motion.span
key={index}
animate={value.length <= 0 ? "visible" : "hidden"}
variants={{
visible: {
y: [0, -10, 0, 10, 0],
transition: {
repeat: Infinity,
duration: 0.5,
},
delay: index * 0.1,
},
hidden: {
opacity: 0,
transition: { duration: 0.25 }
},
}}
>
{letter}
</motion.span>

4. Container Sizing:​

Ensure that the containers have sufficient height to accommodate the input, placeholder overlay, and label without cutting off any elements.

5. Adjust Font Size:​

Sometimes, the font size can affect the perception of motion. Make sure the font size is large enough to notice the animation.

6. Check Browser Compatibility:​

Ensure that the browsers you're testing support the features used in your application. Framer Motion and modern CSS features work well in most modern browsers.

After making these adjustments, you should see the placeholder animation more prominently. If the issue persists, consider inspecting the elements using browser developer tools to see if there are any unexpected layout issues or styling conflicts.
Remember to clear your browser cache and refresh the page after making changes to ensure you're seeing the latest updates.
 

1. Overlay Position:​

Adjust the position of the .auth-placeholder-overlay to be relative to the .auth-input-container. You can use percentage values or other units to ensure the overlay is positioned correctly within its parent container.

CSS:
.auth-input-container {
position: relative;
}

.auth-placeholder-overlay {
position: absolute;
left: 2%;  /* Adjust this value based on your design */
top: 50%;  /* Adjust this value based on your design */
transform: translateY(-50%);
color: #888;
pointer-events: none;
}

2. Adjust the Animation:​

To make the animation more noticeable, you can increase the range of motion. For example, change the y values in the animation to a larger range.

JSX:
y: [0, -10, 0, 10, 0],

3. Verify Animation State:​

Ensure that the animation state is properly toggling between "visible" and "hidden." You might log the animation state to the console or visually inspect it using React DevTools.

JSX:
<motion.span
key={index}
animate={value.length <= 0 ? "visible" : "hidden"}
variants={{
visible: {
y: [0, -10, 0, 10, 0],
transition: {
repeat: Infinity,
duration: 0.5,
},
delay: index * 0.1,
},
hidden: {
opacity: 0,
transition: { duration: 0.25 }
},
}}
>
{letter}
</motion.span>

4. Container Sizing:​

Ensure that the containers have sufficient height to accommodate the input, placeholder overlay, and label without cutting off any elements.

5. Adjust Font Size:​

Sometimes, the font size can affect the perception of motion. Make sure the font size is large enough to notice the animation.

6. Check Browser Compatibility:​

Ensure that the browsers you're testing support the features used in your application. Framer Motion and modern CSS features work well in most modern browsers.

After making these adjustments, you should see the placeholder animation more prominently. If the issue persists, consider inspecting the elements using browser developer tools to see if there are any unexpected layout issues or styling conflicts.
Remember to clear your browser cache and refresh the page after making changes to ensure you're seeing the latest updates.
The letters are moving now, I changed the display of auth-input-placeholder to be inline-flex.
JSX:
import React, { useState } from "react";
// import { useNavigate } from "react-router-dom";

import 'bootstrap/dist/css/bootstrap.min.css';

import Tab from 'react-bootstrap/Tab';
import Tabs from 'react-bootstrap/Tabs';

import "./authentication.css";

import { motion } from "framer-motion";

import OMORISpriteRun from '../assets/sprites/OMORI_Sprite_run.gif';

/*
Keep the branding simple, have an icon of Omori and his friends at the top, or a mini icon of headspace, if you hover over it it transitions between white space and/or black space
If it's Omori and his friends, have them move right to left, then have them face you - down, it's meant to lead the eye to the form below. Maybe play with polyrhythms to get the timing
Make it a modal to the current page
Maybe have an animation to transition and make the form filling visible. Make it similar to the game's main menu
Loop music as well if possible, there should be an mp3 player somewhere, probably at the bottom
Animate the words inside of placeholder
*/

// https://codesandbox.io/p/sandbox/framer-motion-react-wavy-letter-text-animation-j69kkr?file=%2Fsrc%2FWavyText.tsx%3A5%2C16
/*Separate to possibly be used elsewhere, probably gonna rename some parts and animate the input*/
const AuthenticationInput = ({ value, placeholder, onChange, type="text" }) => {
    return (
        <motion.input
            value={value}
            placeholder={placeholder}
            onChange={onChange}
            className={"auth-input"}
            type={type}
        />
    );
}

const AuthenticationPlaceholder = ({ value, placeholder }) => {
    const letters = Array.from(placeholder);

    return (
        <motion.div className="auth-placeholder-overlay">
            {letters.map((letter, index) => (
                // alert(letter + " " + index * 5),
                <motion.span
                    key={index}
                    animate={value.length === 0 ? "visible" : "hidden"}
                    variants={
                        {
                            visible: (index) => ({
                                y: [0, -2, 0, 2, 0],
                                transition: {
                                    repeat: Infinity,
                                    duration: 4,
                                    delay: index * 5
                                }
                            }),
                            hidden: {
                                opacity: 0,
                                transition: { duration: 0.25 }
                            }
                        }
                    }
                >
                    {letter}
                </motion.span>
            ))}
        </motion.div>
    );
};

const AuthenticationInputContainer = ({ value, placeholder, onChange, className, type ="text", error }) => {
    return (
        <div className="auth-input-container">
            <AuthenticationInput
                value={value}
                placeholder=""
                onChange={onChange}
                className={className}
                type={type}
            />
            <AuthenticationPlaceholder value={value} placeholder={placeholder} />
            <label className="auth-error-label">{error}</label>
        </div>
    );
};

const AuthenticationButton = ({ value, onClick, className }) => {
    return (
        <motion.button
            onClick={ onClick }
            className={ className }
            whileHover = {{ scale: 1.1 }}
            whileTap = {{ scale: 0.9 }}
        >{ value }</motion.button>
    );
}

const Authentication = (props) => {
    const [username, setUsername] = useState("")
    const [password, setPassword] = useState("")
    const [usernameError, setUsernameError] = useState("")
    const [passwordError, setPasswordError] = useState("")

    // const navigate = useNavigate();

    const onRegisterButtonClick = () => {
        if (!onCheckValidInput()) {
            return;
        }

        alert('On register button click');
    }

    const onLoginButtonClick = () => {
        if (!onCheckValidInput()) {
            return;
        }

        alert('On login button click');
    }

    const onCheckValidInput = () => {
        // Set initial error values to empty
        setUsernameError('')
        setPasswordError('')

        // Check if the user has entered both fields correctly
        if ('' === username) {
            setUsernameError('Please enter your username.')
            return false;
        }

        if ('' === password) {
            setPasswordError('Please enter a password.')
            return false;
        }

        const password_requirement = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+])[A-Za-z\d!@#$%^&*()_+]{8,}$";

        if (!password.match(password_requirement)) {
            setPasswordError('8 characters minimum; at least 1 uppercase, lowercase, number, and special character.')
            return false;
        }

        return true;
    }

    return <div className={"auth-container"}>
        <div className={"auth-branding-container"}>
            <img src={OMORISpriteRun} />
        </div>
        <Tabs defaultActiveKey="login" variant="tabs" justify>
            <Tab eventKey="login" title="LOGIN">
                <div className={"auth-form-container"}>
                    <AuthenticationInputContainer value={username} placeholder="WHAT IS YOUR NAME?" onChange={ev => setUsername(ev.target.value)} error={usernameError} />                     
                    <AuthenticationInputContainer value={password} placeholder="PASSWORD, PLEASE." onChange={ev => setPassword(ev.target.value)} type={"password"} error={passwordError} />
                </div>
                <div className={"auth-actions-container"}>
                    <AuthenticationButton value={"LOG IN"} onClick={onLoginButtonClick} className={"auth-btn"} />
                </div>
            </Tab>
            <Tab eventKey="register" title="REGISTER">
                <div className={"auth-form-container"}>
                    <AuthenticationInputContainer value={username} placeholder="WHAT IS YOUR NAME?" onChange={ev => setUsername(ev.target.value)} error={usernameError} />
                    <AuthenticationInputContainer value={password} placeholder="PASSWORD, PLEASE." onChange={ev => setPassword(ev.target.value)} type={"password"} error={passwordError}/>
                </div>
                <div className={"auth-actions-container"}>
                    <AuthenticationButton value={"REGISTER"} onClick={onRegisterButtonClick} className={"auth-btn"}/>
                </div>
            </Tab>
        </Tabs>
    </div>
}

export default Authentication;
CSS:
@font-face {
    font-family: 'OMORI';
    src: url('../assets/fonts/OMORI_GAME.ttf') format('truetype'), url('../assets/fonts/OMORI_GAME2.ttf') format('truetype');
}

body {
    font-family: 'OMORI';
    margin: 0;
    align-items: center;
    justify-content: center;
    font-size: 30px;
}

.auth-container {
    width: 30%;
    height: 50%;
    border: 1px solid #ccc;
    border-radius: 8px;
    background-color: #f9f9f9;
    backdrop-filter: drop-shadow();
    padding: 1%;
    flex-direction: column;
    justify-content: center;
    margin: 0 auto;
}

.auth-branding-container {
    /*border: 1px solid grey;*/
    margin: 0% 0% 2.5% 0%;
    display: flex;
    justify-content: inherit;
}

.auth-branding-container img {
}

.auth-form-container {
    margin: 5%;
}

/*Removed original .auth-input-container and made .auth-input-section into it instead*/
.auth-input-container {
    padding: 1%;
    position: relative;
}

/*Originally .auth-input-box*/
.auth-input {
    height: 100%;
    width: 100%;
    border-radius: 8px;
}

.auth-placeholder-overlay {
    position: absolute;
    left: 2.2%;
    top: 4%;
    color: #888;
    pointer-events: none;
    display: inline-flex;
}

.auth-error-label {
    font-size: 70%;
    color: darkred;
    display: block;
}




.auth-actions-container {
    display: flex;
    justify-content: center;
}

.auth-btn {
    border-radius: 8px;
    border: 1px solid transparent;
    padding: 0.6em 1.2em;
    font-size: 100%;
    font-weight: 700;
    cursor: pointer;
    transition: border-color 0.25s;
}

.auth-btn:hover {
    border-color: #646cff;
}

.auth-btn:focus,
.auth-btn:focus-visible {
    outline: 4px auto -webkit-focus-ring-color;
}
Thank you so far, currently though it doesn't really have that wavy motion, every letter looks like it's moving at the same pace. I'm gonna try tinkering around with that.
 
Update: I haven't been able to work on this part for a while, but yea, I do think that in the template that I was using, there's an index.css and App.css and it's probably influencing the Motion Framer. Hopefully I can get back to this in the future.
 
Back
Top Bottom