styled-components Toggle-Restart animation - css

I have a magnifier glass icon, that I'd like to wiggle with a css one time animation. However it only works the first time.
const IconSearch = styled.span`
transform: translate3d(0, 0, 0);
backface-visibility: hidden;
perspective: 1000px;
width: 100%;
height: 100%;
display: inline-block;
color: ${({ theme }) => theme.color.sidebar.search.box.icon};
font-size: 24px;
line-height: 48px;
font-family: "${({ theme }) => theme.font.icon}";
font-weight: 700;
&::before {
content: "\f002";
}
&.a {
animation: wiggle 0.85s cubic-bezier(0.36, 0.07, 0.19, 0.97) both;
}
`;
It is initiated when this.props.wiggle is true.
<IconSearch className={!this.props.wiggle ? "" : "a"}></IconSearch>
The way I am trying to toggle it is like this (Redux Slice reducer):
searchWiggle: (search) => {
if (search.wiggle) {
search.wiggle = false;
search.wiggle = true;
} else {
search.wiggle = true;
}
},
However the state is updated too fast, and the animation does not run after the very first time.
How can I reset the animation, so that it runs every time search.wiggle is updated?

You can use the key prop (that tells React to rerender the component) set with search.wiggle value. (Cf similar issue here)

Related

Styled-components - Over 200 classes were generated, but cant get :hover to work

I came across the error in styled-components :
Over 200 classes were generated for component......
and did the suggested fix from console, and that did the trick, but when I have a container component "Card" that when hovered should change text color of another component "Number" (which has that suggested fix applied, then I cant change the color (i assume because style overrides the hover change, because it works fine with opacity)
the mentioned components are in src/ProgressPieCard (first 2 components)
anyone got any got suggestions, thanks in advance :)
( sorry styling/position is a bit off )
CodeSandBox
const Number = styled.p.attrs<ColorProps>((props) => ({
style: {
color: props.color,
},
}))`
position: absolute;
span {
font-size: 1.5rem;
}
`;
const Card = styled.div.attrs<ColorProps>((props) => ({
style: {
background: props.color,
},
}))`
position: relative;
&:hover {
${Number} {
opacity: 0.5;
// color: red; <-- this dont work
}
}
`;
Values ​​from props were pass as inline styles. They have higher priority. I suggest passing values ​​from props differently. The example below will now work as you wanted.
const Number = styled.p<ColorProps>`
position: absolute;
color: ${p => p.color};
span {
font-size: 1.5rem;
}
`;
const Card = styled.div<ColorProps>`
position: relative;
background: ${p => p.color};
&:hover ${Number} {
opacity: 0.5;
color: red; <-- this WILL work :)
}
`;

CSS Ripple effect with pseudo-element causing reflow

I'm trying to create the material ripple effect with styled-components (which is unable to import the material web-components mixins). I want to stick with using the after element for the foreground effect, to keep the accesibility tree intact.
However, most notably on mobile, the ripple transition is causing reflow in the button's content. It would seem to happen because of the display change (from none to block), but I have tried some alternatives which don't share this artifact, and this side-effect is still present.
Here's my code (I'm using some props to set the ripple, but you can hard-set them if you want to reproduce): [Here was an outdated version of the code]
Thanks for the attention.
Edit: The bug only happens when I add a hover effect to the button, very weird. Below follows the link and a code sample (you will have to set a react repository in order to reproduce it, unfortunately)
https://github.com/Eduardogbg/ripple-hover-reflow-bug
import React, { useRef, useReducer } from 'react';
import ReactDOM from 'react-dom';
import styled from 'styled-components'
const ButtonBase = styled.button`
cursor: pointer;
width: 250px;
height: 6vh;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
outline: none;
position: relative;
overflow: hidden;
border-width: 0;
background-color: cyan;
:hover {
filter: brightness(1.06);
}
::after {
content: '';
pointer-events: none;
width: ${({ ripple }) => ripple.size}px;
height: ${({ ripple }) => ripple.size}px;
display: none;
position: absolute;
left: ${({ ripple }) => ripple.x}px;
top: ${({ ripple }) => ripple.y}px;
border-radius: 50%;
background-color: ${({ ripple }) => ripple.color};
opacity: 0;
animation: ripple ${({ ripple }) => ripple.duration}ms;
}
:focus:not(:active)::after {
display: block;
}
#keyframes ripple {
from {
opacity: 0.75;
transform: scale(0);
}
to {
opacity: 0;
transform: scale(2);
}
}
`
const rippleReducer = ref => (ripple, event) => {
const { x, y, width, height } = ref.current.getBoundingClientRect()
const size = Math.max(width, height)
return {
...ripple,
size,
x: event.pageX - x - size / 2,
y: event.pageY - y - size / 2
}
}
const DEFAULT_RIPPLE = {
size: 0,
x: 0,
y: 0,
color: 'white',
duration: 850
}
const Button = props => {
const ref = useRef(null)
const [ripple, dispatch] = useReducer(
rippleReducer(ref),
{ ...DEFAULT_RIPPLE, ...props.ripple }
)
return (
<ButtonBase
ref={ref}
className={props.className}
ripple={ripple}
onClick={event => {
event.persist()
dispatch(event)
}}
>
{props.children}
</ButtonBase>
)
}
ReactDOM.render(
<div style={{
backgroundColor: 'red',
width: '500px', height: '500px',
display: 'grid',
placeItems: 'center'
}}>
<Button>
<span style={{ fontSize: '30px' }}>
abacabadabaca
</span>
</Button>
</div>,
document.getElementById('root')
);
The problem seems to be related to this chromium bug that was supposedly solved a few years ago: Image moves on hover when changing filter in chrome
Setting transform: translate3d(0,0,0); looks like a fix, though my eye isn't pixel-perfect.

How to run more than one css transition with a click on a button?

I am building an animated hamburger menu with html css js. I now know how to start a css transition with javascript. See https://jsfiddle.net/ralphsmit/byaLfox5/. My problem now is that I need to run more than one transition with a click on my button. I've put my code here https://jsfiddle.net/ralphsmit/v980ouwj/16/.
A short explanation of my code. I have made a button (for the sake of clarity I made it green with a low opacity) and when that button is clicked, the background .dsgn-header-background will appear. Now I also want the two rectangle for the menu to animate into a cross and that the the .dsgn-header-menu-opened-menuitems also fade in.
My question is, how do I modify this js code, so that more than one transition will be started? So all transitions are a different element. You'll find the full code in the JS fiddle above (feel free to edit this).
Javascript:
const background = document.querySelector('.dsgn-header-background');
const button = document.querySelector('.dsgn-header-button');
let open = false;
button.addEventListener('click', onClickPlay);
function onClickPlay(){
if(background.classList.contains('on')){
background.classList.remove('on');
}else{
background.classList.add('on');
}
}
Check this out.
function onClickPlay(){
if(background.classList.contains('on')){
background.classList.remove('on');
element.classList.remove('anotherClassWithDifferentTransitions');
}else{
background.classList.add('on');
element.classList.add('anotherClassWithDifferentTransitions');
}
}
Cheers!
You can try this , The changes is i have added 2 more constant variable which adding on class when menu open and remove on class when menu closes.
const background = document.querySelector('.dsgn-header-background');
const button = document.querySelector('.dsgn-header-button');
const menu_up = document.querySelector('.dsgn-header-rectangle-up');
const menu_down = document.querySelector('.dsgn-header-rectangle-down');
let open = false;
button.addEventListener('click', onClickPlay);
function onClickPlay(){
if(background.classList.contains('on')){
background.classList.remove('on');
menu_up.classList.remove('on');
menu_down.classList.remove('on');
}else{
background.classList.add('on');
menu_up.classList.add('on');
menu_down.classList.add('on');
}
}
hope this will help you .
const content = document.querySelector('.content');
const button = document.querySelector('.dsgn-header-button');
function onClickPlay() {content.classList.toggle('on');}
button.addEventListener('click', onClickPlay);
fiddle: https://jsfiddle.net/s24mbakf/
Add the other elements to your onClickPlay function as you did with demo.
const demo = document.querySelector('.demo');
const demo2 = document.querySelector('.demo2');
const buttondemo = document.querySelector('.buttondemo');
let open = false;
buttondemo.addEventListener('click', onClickPlay);
function onClickPlay(){
if(demo.classList.contains('on')){
demo.classList.remove('on');
demo2.classList.remove('on');
} else {
demo.classList.add('on');
demo2.classList.add('on');
}
}
.demo {
width: 0;
height: 100vh;
background-color: black;
position: absolute;
right: 0;
transition: width 4s;
}
.demo.on {
width: 100vw;
}
.demo2 {
width: 0;
height: 50vh;
background-color: red;
position: absolute;
right: 0;
transition: width 8s;
}
.demo2.on {
width: 100vw;
background-color: yellow;
}
.buttondemo {
position: absolute;
top: 0px;
right: 0px;
width: 100px;
height: 100px;
background-color: green;
}
<div class="demo"><div>
<div class="demo2"><div>
<div class="buttondemo"><div>

How can I update transition-delay value via ReactJS Component?

I am writing a ReactJS component for the first time. I have a tooltip which needs to have a dynamic delay value on mouseenter and mouseleave events. I am currently using a hover approach in CSS with transition-delay. This solution is working for me however, I need to be able to setState and update each of the transition-delay (see below) through my component. I need to be able to accomplish this with pure ReactJS/Javascript (no JQuery etc).
Here is a sample of my code:
.tooltip .tooltiptext {
visibility: hidden;
width: 120px;
background-color: #555;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px 0;
position: absolute;
z-index: 1;
bottom: 125%;
left: 50%;
margin-left: -60px;
opacity: 0;
transition-delay: 2s;
}
.tooltip:hover .tooltiptext {
visibility: visible;
opacity: 1;
transition-delay: 1s;
}
How can I access each of these transition-delay properties from the component and change the value with setState?
Thanks for your help
Update: I have figured out how to update the CSS property through JS. I now need to be able to reset the state. Please see my comment from below.
Here is some additional code:
constructor(props) {
super(props);
this.state = {
userInput: '',
transitionDelay: '0s'
}
handleMouseEnterDelay() {
var mouseIn = document.getElementById('tooltip');
var delayIn = mouseIn.style.transitionDelay = '0s';
this.setState({
transitionDelay: {delayIn}
})
}
handleMouseLeaveDelay() {
var mouseLeave = document.getElementById('tooltiptext');
var delayLeave = mouseLeave.style.transitionDelay = '4s';
this.setState({
transitionDelay: {delayLeave}
})
So what I need is that after each hover event i need transitionDelay to take the values defined in the function. I.e. after first mouseenter/leave event it stays to 4s, so the second time I go to hover(enter) then it is a 4s delay for both enter and leave. I need the enter delay to go back to 0s as defined in the handleMouseEnterDelay function.
Is there a way which I can have two 'transitionDelay' values in setState? I tried a nested object i.e. in:{transitionDelay}, out:{transitionDelay} but i couldn't access it while setting state.
Maybe something like...
constructor() {
this.state = {
style: {
transitionDelay: '1s'
}
};
this.toggleDelay = this.toggleDelay.bind(this);
}
toggleDelay(state) {
this.setState({ style: { transitionDelay: state ? '2s' : '1s' } });
}
...
render() {
return (
<div className="tooltip" style={this.state.style} onMouseEnter={() => this.toggleDelay(true)} onMouseLeave={() => this.toggleDelay(false)}>
.....
</div>
);
}

transition for translateY(0) working, but not translateY(-50px)

I'm having some trouble with a transition on a transform. I'm making a tic-tac-toe game like in the freecodecamp front-end challenges: https://codepen.io/freeCodeCamp/full/KzXQgy.
I've been able to create most layout things no problem, but am having an issue with my transition on a div that shows which players' turn it is after hitting the reset button. Right now I'm just working on two player mode, so I click two players, then X or O, and then the tic-tac-toe board shows up and a div transform: translateY(-50px) to indicate whether it's Player 1 or Player 2's turn (based on a random number variable I set up). The first time through the div transition's perfectly. Then I hit the Reset All div and it takes me back to the beginning to choose how many players again. And the div transitions the transform: translateY(0) perfectly back to it's starting position. Where I'm struggling is, now when I cycle through the options again, if it's Player 1 or Player 2's turn again, the transition never happens and the div just transforms -50px up with the translateY.
I've tried everything I could think of, JS setting up the transition, resetting the transition, moving the transition to be on different classes, adding and removing a class that only has a transition on it. Can't figure it out, but the weird thing is, whenever I hit the "Reset All", the transform transitions back to 0px normally. Here's my codepen: https://codepen.io/rorschach1234/pen/dZMaJg?editors=0111. I know it's still very rough, but just can't figure out this transition problem. Really appreciate any help. Thanks everyone!
My relevant Html:
<div class="container">
<div class="turns">
<div class="turns__left turns__box"></div>
<div class="turns__right turns__box"></div>
</div>
</div>
My relevant CSS:
.turns {
width: 100%;
display: flex;
position: absolute;
justify-content: space-around;
top: 0;
left: 0;
z-index: -1;
&__box {
width: 40%;
height: 50px;
display: flex;
justify-content: center;
align-items: center;
color: white;
font-size: 1.3em;
font-family: sans-serif;
transition: transform 1s .3s ease;
}
&__left {
background-color: $color-turn-left;
//transition: transform 1s .3s ease;
}
&__right {
background-color: $color-turn-right;
//transition: transform 1s .3s ease;
}
}
My Javascript:
let numOfPlayers = document.querySelectorAll(".player .choices h2");
let playerScreen = document.querySelector(".player");
let markerScreen = document.querySelector(".markers");
let singlePlayer = true;
let backBtn = document.querySelector(".markers__back");
let gameScreen = document.querySelector(".game");
let playerOne; let playerTwo; let activePlayer;
let turnBoxes = document.querySelectorAll(".turns__box");
let resetBtn = document.querySelector(".scoreboard__reset");
game();
function game() {
playerModeSelection();
markerSelection();
}
function boardChange(active, inactive) {
inactive.style.opacity = "0";
inactive.style.zIndex = "0";
active.style.opacity = "1";
active.style.zIndex = "1";
}
//One or Two player Selection & transition to Marker Selector
function playerModeSelection() {
for (let i = 0; i < numOfPlayers.length; i++) {
numOfPlayers[i].addEventListener("click", function() {
if(i === 1) {
singlePlayer = false;
}
boardChange(markerScreen, playerScreen);
})
}
}
function markerSelection() {
//Back Button Functionality
backBtn.addEventListener("click", function() {
boardChange(playerScreen, markerScreen);
})
//Listen for choice of X or O
for (let i = 0; i < markers.length; i ++) {
markers[i].addEventListener("click", function() {
boardChange(gameScreen, markerScreen);
if (i === 1) {
playerArr = ["O", "X"];
}
//Starts Two Player Game; Here begin is the function that calls transition
if(!singlePlayer) {
twoPlayerMode();
}
})
}
}
function twoPlayerMode() {
activePlayer = Math.floor(Math.random() * 2);
turnBoxes[activePlayer].textContent = "Go Player " + (activePlayer + 1) + "!";
turnBoxes[activePlayer].style.transform = "translateY(-50px)";
resetBtn.addEventListener("click", function() {
boardChange(playerScreen, gameScreen);
turnBoxes[activePlayer].style.transform = "translateY(0px)";
})
}

Resources