Content of div gets shifted to the left after a scale animation - css

I have a div in which, after applying a scale() transform (using react-spring) to it on mouseOver and mouseLeave, the div's contents shift 1 or 2 pixels to the left after returning to the original scale(1) position.
Gif of div's behavior
Here's the Card component (using styled-components):
const Card = styled.div`
position: relative;
display: block;
width: 100%;
text-align: left;
margin: 16px 0 32px 0;
padding: 14px 24px 16px 24px;
border: 1px solid hsla(0, 0%, 92%, 1);
border-radius: 8px;
color: ${props => props.theme.black};
background: #fff;
transition: transform 0.18s ease-in-out, box-shadow 0.18s ease-in-out;
span {
font-family: ${props => props.theme.headerFont};
text-transform: uppercase;
font-weight: 500;
font-size: 14px;
letter-spacing: 0.1em;
text-align: left;
color: #0c344b;
opacity: 0.45;
b {
font-size: 12px;
}
}
${media.tablet`
padding: 12px 30px 22px 30px;
`}
`
And here's where it's being rendered:
export default function Article({
date = '',
title = '',
timeToRead = '',
excerpt = '',
slug = ''
}) {
const [hover, setHover] = useState(false)
const props = useSpring({
transform: `scale(${hover ? 1.05 : 1})`,
borderRadius: `8px`,
boxShadow: `${hover ? `1px 1px 14px #ededed` : `0px 2px 8px #f0f0f0`}`,
config: {
mass: 1,
tension: 350,
friction: 40
}
})
return (
<Link to={slug}>
<animated.div style={props}>
<Card
onMouseOver={() => setHover(true)}
onMouseLeave={() => setHover(false)}
>
<span>{date}</span>
<span
css={`
float: right;
`}
>
<b role="img" aria-label="Time to read">
🕙
</b>{' '}
{timeToRead} min
</span>
<h2
css={`
margin: 0;
font-weight: 800;
font-size: 1.4em;
padding: 4px 0 8px 0;
`}
>
{title}
</h2>
<p
css={`
margin: 0;
line-height: 26px;
font-size: 16px;
`}
dangerouslySetInnerHTML={{ __html: excerpt }}
/>
</Card>
</animated.div>
</Link>
)
}
Any insight would be deeply appreciated!
EDIT: I should add that, if I leave the mouseOut scale around 1.0, i.e. 1.00001 or 0.99999, the contents do not shift as was described. Ideally I want to fix this without resorting to this hack, but if I leave it this way, should I expect a performance hit?

Related

Animate item to center on click

so I have this three button I need to find a way any of these two solution
make animation so that each time one button come in front and the
two other go to the back
or, when I click on a button it come to the front and the other go to the back
here is my code for the style
.index-environement-specialise-boutton-main {
width: 279px;
height: 76px;
border: 1px solid var(--color-text-primary);
border-radius: 46px;
text-align: center;
font-style: normal !important;
font-variant: normal !important;
font-weight: bold !important;
font-size: 32px !important;
line-height: 34px !important;
font-family: Poppins !important;
letter-spacing: 0.5px;
color: var(--color-text-primary);
opacity: 1;
margin-inline: 2.625rem;
position: relative;
box-shadow: 0px 0px 222px 150px rgba(193, 186, 243, 0.75);
-webkit-box-shadow: 0px 0px 222px 150px rgba(193, 186, 243, 0.75);
-moz-box-shadow: 0px 0px 222px 150px rgba(193, 186, 243, 0.75);
display: flex;
align-items: center;
justify-content: center;
}
.index-environement-specialise-boutton-main.b1 {
background: #C1BAF3 0% 0% no-repeat padding-box;
}
.index-environement-specialise-boutton-main.b2 {
background: #F8D6B5 0% 0% no-repeat padding-box;
}
.index-environement-specialise-boutton-main.b3 {
background: #F3B9C5 0% 0% no-repeat padding-box;
}
.index-environement-specialise-boutton-behind {
width: 176px;
height: 48px;
border: 1px solid var(--color-text-primary);
border-radius: 46px;
text-align: center;
font-style: normal !important;
font-variant: normal !important;
font-weight: bold !important;
font-size: 20px !important;
line-height: 21px !important;
font-family: Poppins !important;
letter-spacing: 0.5px;
color: var(--color-text-primary);
opacity: 1;
display: flex;
align-items: center;
justify-content: center;
}
.index-environement-specialise-boutton-behind.b1 {
background: #C1BAF3 0% 0% no-repeat padding-box;
}
.index-environement-specialise-boutton-behind.b2 {
background: #F8D6B5 0% 0% no-repeat padding-box;
}
.index-environement-specialise-boutton-behind.b3 {
background: #F3B9C5 0% 0% no-repeat padding-box;
}
.index-environement-specialise-boutton-main:hover,
.index-environement-specialise-boutton-behind:hover {
color: var(--color-text-primary);
}
.index-environement-specialise-col-right{
width:700px;
}
<div class="w-100 index-environement-specialise-col-right">
<a class="index-environement-specialise-boutton-behind b2"
role="button"
href="javascript:void(0)"
id="A2" runat="server" title="hôtesses">
hôtesses
</a>
<a class="index-environement-specialise-boutton-main b1"
role="button"
href="javascript:void(0)"
id="A3" runat="server" title="sitters">
sitters
</a>
<a class="index-environement-specialise-boutton-behind b3"
role="button"
href="javascript:void(0)" id="A4" runat="server"
title="vendeurs">
vendeurs
</a>
</div>
With some CSS flex positioning, with JS's getBoundingClientRect() method and some simple math — animate the buttons wrapper using CSS3 transition and transform (inside a .carousel container) by the center point of each button item in relation to the horizontal center of the carousel:
// DOM helpers:
const els = (sel, par) => (par || document).querySelectorAll(sel);
const el = (sel, par) => (par || document).querySelector(sel);
// Carousel:
const btnCarousel = (elCarousel) => {
const move = (evt) => {
const elButton = evt.target.closest("button");
if (!elButton) return; // do nothing
const elSlider = el(".carousel-slider", elCarousel);
const bcrButton = elButton.getBoundingClientRect();
const bcrCarousel = elCarousel.getBoundingClientRect();
const bcrSlider = elSlider.getBoundingClientRect();
const centerCarousel = bcrCarousel.width / 2;
const centerButton = bcrButton.width / 2;
const xButton = bcrButton.left;
const xSlider = bcrSlider.left;
const x = centerCarousel - xButton - centerButton + xSlider;
elSlider.style.translate = `${x}px 0`;
};
elCarousel.addEventListener("click", move);
};
els(".carousel").forEach(btnCarousel);
.carousel {
position: relative;
overflow: hidden;
}
.carousel-slider {
display: flex;
margin: 0 auto;
justify-content: center;
gap: 1rem;
transition: 0.5s;
}
<div class="carousel">
<div class="carousel-slider">
<button type="button">1 Button</button>
<button type="button">2 Btn</button>
<button type="button">3 Button longer text</button>
</div>
</div>
<div class="carousel">
<div class="carousel-slider">
<button type="button">1 Super Button</button>
<button type="button">2 Some Button</button>
<button type="button">3</button>
</div>
</div>
Add a .is-active on click, some more styles, a CSS transition-origin to the button's center-top and you're almost done:
// DOM helpers:
const els = (sel, par) => (par || document).querySelectorAll(sel);
const el = (sel, par) => (par || document).querySelector(sel);
// Carousel:
const btnCarousel = (elCarousel) => {
const elButtons = els("button", elCarousel);
const move = (evt) => {
const elButton = evt.target.closest("button");
if (!elButton) return; // do nothing
const elSlider = el(".carousel-slider", elCarousel);
const bcrButton = elButton.getBoundingClientRect();
const bcrCarousel = elCarousel.getBoundingClientRect();
const bcrSlider = elSlider.getBoundingClientRect();
const centerCarousel = bcrCarousel.width / 2;
const centerButton = bcrButton.width / 2;
const xButton = bcrButton.left;
const xSlider = bcrSlider.left;
const x = centerCarousel - xButton - centerButton + xSlider;
elSlider.style.translate = `${x}px 0`;
elButtons.forEach(el => el.classList.remove("is-active"));
elButton.classList.add("is-active");
};
elCarousel.addEventListener("click", move);
};
els(".carousel").forEach(btnCarousel);
/* QuickReset*/
*,
::before,
::after {
margin: 0;
box-sizing: border-box;
}
body {
font: 16px/1.5 sans-serif;
background: linear-gradient(90deg, rgba(244, 246, 255, 1) 0%, rgba(213, 208, 247, 1) 50%, rgba(244, 246, 255, 1) 100%);
}
.carousel {
position: relative;
overflow: hidden;
padding: 4rem 0;
}
.carousel-slider {
display: flex;
margin: 0 auto;
justify-content: center;
gap: 4rem;
transition: 0.5s;
}
.btn {
border: 0.1rem solid #455171;
padding: 0.4em 2.6em;
font-weight: 900;
font-size: 1rem;
border-radius: 2em;
cursor: pointer;
}
.carousel .btn {
transform-origin: center top;
transition: 0.5s;
}
.carousel .btn.is-active {
scale: 1.4;
}
.bg-1 {
background-color: #ecb9cc;
}
.bg-2 {
background-color: #c1baf3;
}
.bg-3 {
background-color: #f0d2be;
}
<div class="carousel">
<div class="carousel-slider">
<button type="button" class="btn bg-1">Hôtesses</button>
<button type="button" class="btn bg-2 is-active">Sitters</button>
<button type="button" class="btn bg-3">Vendeurs</button>
</div>
</div>

Is there a way to change the background color of a parent element when a radio button is clicked without jQuery?

The code structure looks somewhat like this
function RenderOptions() {
return allOptions.map((val) => {
return (
<>
<label htmlFor={val}>
<input
type="radio"
name="options"
id={val}
value={val}
className="check-btn"
/>{" "}
<span className="text-inner">{val}</span>
</label>{" "}
</>
);
});
}
Didn't put a div since I didn't want to spend some time finding how to align the buttons in rows.
CSS
.check-btn {
opacity: 0;
position: relative;
top: 2px;
}
label {
border-radius: 10px;
font-family: Inter;
font-style: normal;
font-weight: 600;
font-size: 12px;
line-height: 12px;
text-align: center;
width: 100%;
transition: 0.3s ease;
background: #f5f7fb;
border: 0.794239px solid #4d5b9e;
box-sizing: border-box;
border-radius: 7.94239px;
padding-top: 5px;
padding-bottom: 5px;
padding: 5px 10px 5px 10px;
}
label:hover {
background-color: #d6dbf5;
cursor: pointer;
}
.text-inner {
position: relative;
right: 10px;
}
I'm trying to change the background color of the parent label when the radio button is checked. Can do it the CSS way or the React way provided only 1 button from each sets color changes.
and if another option is clicked ->
TL;DR. See the CodePen sample I prepared for you here, and the snippet below.
In this example, I simply used the useState hook to keep track of the current radio selection, and applied its value as data attribute to label tag. Then you can use CSS to style it as needed.
const App = () => {
const [chosenOne, setChosenOne] = React.useState();
const list = ["Alec Baldwin", "Bruce Willis", "Dwayne Johnson", "Jamie Fox"];
return (
<div>
{list.map((person, i) => (
<label key={i} htmlFor={person} data-chosen={chosenOne === person}>
<input type="radio" name="options" id={person} value={person} className="check-btn" onChange={(e) => setChosenOne(e.target.value)}/>
<span className="text-inner">{person}</span>
</label>
))}
</div>
)
}
.check-btn {
opacity: 0;
position: relative;
top: 2px;
}
label {
border-radius: 10px;
font-family: Inter;
font-style: normal;
font-weight: 600;
font-size: 12px;
line-height: 12px;
text-align: center;
width: 100%;
transition: 0.3s ease;
background: #f5f7fb;
border: 0.794239px solid #4d5b9e;
box-sizing: border-box;
border-radius: 7.94239px;
padding-top: 5px;
padding-bottom: 5px;
padding: 5px 10px 5px 10px;
}
label:hover, label[data-chosen="true"] {
background-color: #d6dbf5;
cursor: pointer;
}
.text-inner {
position: relative;
right: 10px;
}
label + label {
margin-left: .5rem;
}
You could do that with CSS or the style prop in React.
I'm assuming your storing the state of the radio buttons somewhere?
When you map through the array, you can check if the current radio button's value is equal to the value of the current state. If they are the same, you can add the class or style to style the label.

Reactjs Styled-components not working properly

I have a styled checkbox and I want to set its background to white when checked:false and to green when checked:true
But the problem is background color is always staying green and i dont know why.
The react component in which I'm using this checkbox component is acting right,Its just background color that i cant adjust
Any suggestions please?
const CheckboxContainer = styled.div`
display: inline-block;
vertical-align: middle;
`
const Icon = styled.svg`
fill: none;
stroke: white;
stroke-width: 2px;
`
const HiddenCheckbox = styled.input.attrs({ type: 'checkbox' })`
border: 0;
clip: rect(0 0 0 0);
clippath: inset(50%);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
white-space: nowrap;
width: 1px;
`
const StyledCheckbox = styled.div`
display: inline-block;
width: 16px;
height: 16px;
background: #ffffff;
border-radius: 3px;
transition: all 150ms;
margin-top: 10px;
${HiddenCheckbox}:focus + & {
background: ${({ checked }) => (checked ? '#06ba63' : '#ffffff')};
}
${Icon} {
visibility: ${({ checked }) => (checked ? 'visible' : 'hidden')};
}
`
const Checkbox = ({ className, checked, ...props }) => (
<CheckboxContainer className={className}>
<HiddenCheckbox checked={checked} {...props} />
<StyledCheckbox checked={checked}>
<Icon viewBox="0 0 24 24">
<polyline points="20 6 9 17 4 12" />
</Icon>
</StyledCheckbox>
</CheckboxContainer>
)
export default Checkbox
you should check for checked instead of focus of the checkbox :
${HiddenCheckbox}:checked + & {
background: ${({ checked }) => (checked ? '#06ba63' : '#ffffff')};
}
If both Hidden and Styled checkboxes are get the same value from checked, you only need to use the value of checked passed to StyledCheckbox (like you do with Icon):
const StyledCheckbox = styled.div`
display: inline-block;
width: 16px;
height: 16px;
background: #ffffff;
border-radius: 3px;
transition: all 150ms;
margin-top: 10px;
background: ${({ checked }) => (checked ? '#06ba63' : '#ffffff')};
${Icon} {
visibility: ${({ checked }) => (checked ? 'visible' : 'hidden')};
}
`
The other option is only to apply the style if HiddenCheckbox is checked via CSS selectors:
const StyledCheckbox = styled.div`
display: inline-block;
width: 16px;
height: 16px;
background: #ffffff;
border-radius: 3px;
transition: all 150ms;
margin-top: 10px;
background: white;
${Icon} {
visibility: hidden;
}
${HiddenCheckbox}:checked + & {
background: #06ba63;
}
${HiddenCheckbox}:checked + & > ${Icon} {
visibility: visible;
}
`

React: Caret icon keeps moving outside of dropdown menu whenever page is resized to iPad or smaller

I have a styled react component that features a hand rolled dropdown menu including a caret icon and every time I test the responsiveness of the dropdown the caret icon pops outside. I've wrapped the dropdown inside of a larger component to place everything on the same line and then included the icon inside of the styled select component. I've used this methodology in another app and the dropdown is completely responsive and doesn't have any issues with icon placement so I'm completely scratching my head with this one. Any help/suggestions would be great
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import { FaCaretDown } from 'react-icons/fa'
import Table from '../core/Table'
import SubHeader from '../elements/SubHeader'
import EligibleOffer from './EligibleOffer'
import { tableDataProps, tableColumnProps } from '../../propTypes'
const BUNDLE_KEY = 'bundle'
const rowKey = 'id'
class OfferBundlesTable extends Component {
static propTypes = {
bundles: PropTypes.arrayOf(tableDataProps),
columns: tableColumnProps,
viewedOfferIds: PropTypes.arrayOf(PropTypes.string),
onBundleSelect: PropTypes.func
}
static defaultProps = {
viewedOfferIds: []
}
renderCell = (cell = {}) => (
<div>
{cell.title && <SubHeader>{cell.title}</SubHeader>}
<p>{cell.details}</p>
</div>
)
renderBundle = bundle => (
<StyledOffer
offer={bundle}
onOfferSelect={this.props.onBundleSelect}
viewed={this.props.viewedOfferIds.includes(bundle.OfferId)}
/>
)
getColumns = columns =>
Object.entries(columns).reduce(
(cols, [key, value]) => ({
...cols,
[key]: {
...value,
renderCell: key === BUNDLE_KEY ? this.renderBundle : this.renderCell
}
}),
{}
)
render() {
const { bundles = [], columns = [] } = this.props
return (
<div>
<DropdownRow>
<StyledSelect>
<StyledStrong>SORT BY: </StyledStrong>
<select>
<option>Default</option>
<option>Promo Price (High to Low)</option>
<option>Promo Price (Low to High)</option>
<option>Alphabetically (A-Z)</option>
<option>Alphabetically (Z-A)</option>
<option>Internet Tier (High to Low)</option>
<option>Internet Tier (Low to High)</option>
</select>
<StyledSelectIcon size="1.5rem" />
</StyledSelect>
<StyledSelect>
<StyledStrong>CONTRACT TERMS (MONTHS):</StyledStrong>
<select>
<option>36</option>
<option>24</option>
<option>12</option>
</select>
<StyledSelectIcon size="1.5rem" />
</StyledSelect>
</DropdownRow>
<StyledTable
rowKey={rowKey}
rows={bundles}
columns={this.getColumns(columns)}
/>
</div>
)
}
}
const StyledTable = styled(Table)`
th:first-child {
width: 25%;
}
`
const StyledOffer = styled(EligibleOffer)`
margin-bottom: 15px;
`
const DropdownRow = styled.div`
th:first-child {
width: 25%;
}
flex: 1;
display: flex;
align-items: stretch;
padding: 20px 30px 20px 10px;
width: 60%;
margin-left: 42%;
`
const StyledSelectIcon = styled(FaCaretDown)`
position: absolute;
top: 0;
right: 10px;
bottom: 0;
margin: auto;
color: black;
pointer-events: none;
`
const StyledSelect = styled.div`
position: relative;
margin-left: auto;
select {
padding: 5px 45px 5px 10px;
font-size: 1.2rem;
line-height: 1.4rem;
font-weight: 500;
color: black;
border: 2px solid black;
height: 36px;
appearance: none;
background: transparent;
border-radius: 0;
}
`
const StyledStrong = styled.strong`
padding-right: 5px;
`
export default OfferBundlesTable
After looking at your code, I would suggest an alternative approach, including the caret in the css for your select. This makes it impossible for it to move away from the select, as it's now part of it. Run the snippet below to see it work--of course you would want to put that stuff into the styled-component css definition, and remove the <StyledSelectIcon/> altogether (here's a forked stackblitz).
Styled component definition:
const StyledSelect = styled.div`
position: relative;
margin-left: auto;
select {
padding: 5px 45px 5px 10px;
font-size: 1.2rem;
line-height: 1.4rem;
font-weight: 500;
color: black;
border: 2px solid black;
height: 36px;
background-color: transparent;
border-radius: 0;
-moz-appearance: none;
-webkit-appearance:none;
appearance: none;
background-image: url('data:image/svg+xml;utf8,<svg fill="black" viewBox="0 0 320 512" height="1.5rem" width="1.5rem" xmlns="http://www.w3.org/2000/svg"><path d="M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z"></path></svg>');
background-repeat: no-repeat;
background-position: right 8px center;
}
`
Snippet to run here:
.customselect{
padding: 5px 45px 5px 10px;
font-size: 1.2rem;
line-height: 1.4rem;
font-weight: 500;
color: black;
border: 2px solid black;
height: 36px;
background-color: transparent;
border-radius: 0;
-moz-appearance: none;
-webkit-appearance:none;
appearance: none;
background-image: url('data:image/svg+xml;utf8,<svg fill="black" viewBox="0 0 320 512" height="1.5rem" width="1.5rem" xmlns="http://www.w3.org/2000/svg"><path d="M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z"></path></svg>');
background-repeat: no-repeat;
background-position: right 8px center;
}
<select class="customselect">
<option>Option A</option>
<option>Option B</option>
<option>Option C</option>
<option>Option D</option>
</select>

Is there a way to modify '&:before/:after' to give a dynamic property?

A styled-component that has &:before attribute that I want to give access to a dynamic color property. But I'm lost to how to apply that.
I've Tried passing the props to <Hover /> and it works. But the Triangle in &:before cannot access that.
const Hover = styled.div`
padding:7px;
margin: -102px auto;
border-style: solid;
border-radius: 15px;
border-width: 3px;
text-align: left;
font-weight: 400;
font-size: 19px;
color: #000000;
font-family: sans-serif;
position: absolute;
left:-15px;
z-index:10;
&:before {
content: "";
width: 0px;
height: 0px;
position: absolute;
border-left: 10px solid ;
border-right: 10px solid transparent;
border-top: 10px solid ;
border-bottom: 10px solid transparent;
left: 19px;
bottom: -19px;
z-index:10;
}
`;
As a single styled-component the following class is:
class MarkerHover extends Component {
render() {
const {color, children}= this.props
return (
<Hover style={{backgroundColor: color }}>
{...children}
</Hover>
);
}
}
export default MarkerHover;
I expect to have a whole colored Window after successfully passing the color props to the &:before division.
As the documentation of Styled Components states (see https://www.styled-components.com/docs/basics#passed-props) you can read passed props:
const Hover = styled.div`
color: ${props => props.color};
`;
when you pass them like this:
<Hover color="#f00" />
You can use that same prop in your pseudo-element as well:
const Hover = styled.div`
color: ${props => props.color};
&::before {
background: ${props => props.color};
}
`;
So don't use the style attribute on your styled component, but pass regular props.
If you want to apply a CSS rule only after a certain condition, you can do that like this:
const Hover = styled.div`
color: #ccc;
&::before {
content: "";
padding: 16px;
${props => props.withBg ? `background-color: ${props.withBg}` : ''}
}
`;
With background:
<Hover withBg="#f00" />
Without background:
<Hover />

Resources