Not long ago here I recieved answer, how increase each img as member of array. Somehow the same principle don't wokrs for z-index (increaced img should lie on top of the rest), though console displays that z-index changed. Why?
class Article extends React.Component{
constructor(props) {
super(props)
this.state = {showIncreaced: null}
this.getImgStyle = this.getImgStyle.bind(this);
this.increase = this.increase.bind(this);
}
increase (incId) {
this.setState({showIncreaced: incId})
}
getImgStyle (id) {
return {
width: '20vw',
marginRight: '0.5vw',
marginLeft: '0.5vw',
zIndex: this.state.showIncreaced === id ? '10' : '0',
transform: this.state.showIncreaced === id ? 'scale(1.5, 1.5)' : 'scale(1, 1)'
};
}
render(){
const TipStyle={
marginBottom: '10px'
}
return(
<div style={TipStyle}>
<h2 style={{marginBottom: '1px'}}>{this.props.name}</h2>
<div>
{[1,2,3].map((id) => {
return <img style={this.getImgStyle(id)} src={this.props[`img${id}`]} onMouseOver={this.increase.bind(this, id)} onMouseOut={this.increase} />
})}
</div>
</div>
);
}
}
https://jsfiddle.net/Nata_Hamster/fbs3m4jL/
add position: 'relative', to the object returned by getImgStyle because z-index works only when postion is set to something else than static (its default value). The easiest way is to use relative because the element is still part of the document flow.
https://jsfiddle.net/fbs3m4jL/7/
This is because the position of your image elements are static by default.
If you update the image position to say position:absolute;, then the zIndex values will work as expected. The catch with this is that you need to position the images with left coordinates so that they sit next to each other. Here is an updated version of getImgStyle that illustrates the concept:
getImgStyle (id) {
return {
position:'absolute', // Set absolute position
left: `${(id-1) * 100}px`, // Calculate a left coordinate for image
width: '20vw',
marginRight: '0.5vw',
marginLeft: '0.5vw',
zIndex: this.state.showIncreaced === id ? '10' : '0',
transform: this.state.showIncreaced === id ? 'scale(1.5, 1.5)' : 'scale(1, 1)'
};
}
Here is a working jsFiddle if you'd like to see it in action
Related
I'm trying to create a "collapsible text" react component that allows a user-determined number of lines to be displayed. I'm using the line-clamp CSS property to do this, for the most part. On the JavaScript side of things, I want to selectively render a button that toggles the effect based on whether the content to be shown is greater in height than the number of lines to be shown multiplied by their line height. This is working fairly well in Firefox and Chrome. I can get the line height of the element after it's rendered, and I can multiply that by the number of lines that the user wants shown to approximate the height of the content. I can use that to set a min-height CSS property, and I can compare that value against the scroll height of the content. The problem is, I'm getting the same value for the scroll height of the content in Firefox and Chrome, but NOT in Safari.
const CollapsibleText = ({
text,
linesToShow,
markdown = false,
containerStyles,
textStyles,
buttonStyles,
handleScroll,
}) => {
const [isActive, setIsActive] = useState(false)
const contentRef = useRef(null)
const [displayButton, setDisplayButton] = useState(false)
const [contentMinimumHeight, setContentMinimumHeight] = useState(null)
const windowSize = useWindowSize()
const linesShown = windowSize.mobile
? parseInt(linesToShow[0])
: parseInt(linesToShow[1])
const handleToggleIsActive = () => {
if (isActive && handleScroll) {
handleScroll()
}
setIsActive(!isActive)
}
useEffect(() => {
const contentLineHeight = parseInt(
window
.getComputedStyle(contentRef.current, null)
.getPropertyValue('line-height')
)
const contentHeight = contentRef.current.scrollHeight
// Adding 5 here to offset rounding that seems to occur in actual pixel values of rendered output, value of 5 is arbitrary.
const linesToShowHeight = contentLineHeight * linesShown + 5
setContentMinimumHeight(linesToShowHeight)
console.log('Content height: ', contentHeight)
console.log('Lines to show height: ', linesToShowHeight)
if (contentHeight > linesToShowHeight) {
setDisplayButton(true)
}
if (contentHeight < linesToShowHeight) {
setDisplayButton(false)
}
}, [windowSize])
return (
<div sx={{ ...containerStyles }}>
<div
sx={{
display: '-webkit-box',
'-webkit-line-clamp': !isActive ? linesToShow : 'none',
lineClamp: !isActive ? linesToShow : 'none',
'-webkit-box-orient': 'vertical',
overflow: 'hidden',
outline: '2px solid red',
textOverflow: 'ellipsis',
minHeight: contentMinimumHeight,
'&:first-child': {
marginTop: '0px',
},
}}
>
{markdown ? (
<div
ref={contentRef}
sx={{
...textStyles,
'& *:first-child': {
marginTop: '0px',
// marginBottom: '0px',
},
}}
dangerouslySetInnerHTML={{
__html: text,
}}
/>
) : (
<p
ref={contentRef}
sx={{
whiteSpace: 'pre-wrap',
...textStyles,
}}
>
{text}{' '}
</p>
)}
</div>
{displayButton && (
<Button
variant="viewMore"
sx={{ marginTop: '20px', ...buttonStyles }}
onClick={handleToggleIsActive}
>
{!isActive ? 'Read more' : 'Read Less'}{' '}
<ChevronDown
styles={{ transform: !isActive ? 'none' : 'rotate(180deg)' }}
/>
</Button>
)}
</div>
)
}
export default CollapsibleText
To determine the content height, I'm using a ref and grabbing the scroll height of the element in question. I get the same value in Chrome and Firefox, but a larger value in Safari, which breaks my ability to selectively render the "toggle" button I'm trying to implement.
I'm comparing the "line height" (with my admittedly very simplistic algorithm) against the content height to determine whether or not to render the button. I get the same measurement in all three browsers, so as far as I can tell Safari is just measuring the scroll height of the content differently than in Chrome and Firefox.
I seemingly have to use a min height because the line-clamp property causes the content to collapse to zero in Safari. Setting the min height allows things to work more or less as predicted where the content is more than the user-defined lines to show prop given to the component.
Screenshots of the console logs for the SAME content in each browser:
Firefox:
Chrome:
Safari:
I'm just starting out with React hooks and I'm trying to make a shape shifting button, when you click on it it changes from square to rounded and back. I want to achieve the effect by changing the border-radius CSS property but it does not seem to work!
I'm puzzled because using similar code I can get the color to change...Please help me!
function ShapeChangerButton(){
const [radius, setRadius] = useState("0px");
function changeShape(){
if(radius === "0px"){
setRadius(radius => "30px");
}
else{
setRadius(radius => "0px");
}
}
return(
<div>
<button className="standardBtn"
style={{border-radius: radius}}
onClick={changeShape}>change shape</button>
</div>
)
}
The style config shoule be :
style={{ borderRadius: radius }}
or
function ShapeChangerButton() {
const [radius, setRadius] = useState("0px");
function changeShape() {
if (radius === "0px") {
setRadius((radius) => "30px");
} else {
setRadius((radius) => "0px");
}
}
return (
<div>
<button
className="standardBtn"
style={{ borderRadius: radius }}
onClick={changeShape}
>
change shape
</button>
</div>
);
}
On android emulator, my React Native 0.62.2 app displays uploaded images within an accordion whose display area is warped with <View> which is styled with width and height. The image is warped with which is <Image> with cache ability. The problem is that the image is positioned way below beyond the viewing boundary and is not visible when open the accordion.
Here is the render code in accordion which is provided with both width and height as canvas:
return (
<>
<TouchableWithoutFeedback onPress={() => toggleListItem()}>
<View style={styles.titleContainer}>
<Text>{title}</Text>
<Animated.View style={{ transform: [{ rotateZ: arrowAngle }] }}>
<Icon name="chevron-down-outline" size={20} />
</Animated.View>
</View>
</TouchableWithoutFeedback>
<Animated.View style={[styles.bodyBackground, { height: bodyHeight }]}>
<View
style={(absPosition ? styles.bodyContainerAbs : styles.bodyContainerCenter), (screenSize ? {width:screenSize.width, height:screenSize.height, flex:1} : null)}
onLayout={event => {
if (screenSize) {
setBodySectionHeight(screenSize.height);
} else {
setBodySectionHeight(event.nativeEvent.layout.height);
};
console.log("layout : ", event.nativeEvent.layout);
}
}> //<<<===here is <View> warping the image area. Both width and height are set.
{children} //<<<===images uploaded displayed here
</View>
</Animated.View>
</>
);
const styles = StyleSheet.create({
bodyBackground: {
backgroundColor: '#EFEFEF',
overflow: 'hidden',
},
bodyContainerCenter: { //<<<==here is the style used for display images
padding: 1,
paddingLeft: 0,
justifyContent: 'center',
alignItems: 'center',
},
bodyContainerAbs: {
padding: 5,
paddingLeft: 10,
position: 'absolute',
bottom: 0,
},
});
Here is the image render code:
const displayImg = (img_source, width, ht, index) => {
if (img_source && img_source!==[] && img_source!=={}) {
return (
<ImageZoom cropWidth={screen_width}
cropHeight={screen_ht}
imageWidth={width}
imageHeight={ht}
enableSwipeDown={true}
style={{padding:1}}
>
<FastImage
source={{uri:img_source}}
resizeMode={FastImage.resizeMode.contain}
style={{ //<<<===image width and height are set
width:width,
height:ht,
position:"absolute" //<<<===does not work
}}
/>
</ImageZoom>
);
} else {
return null;
};
};
When there is one image, it is display with full screen width. For 2 images, then they are displayed side by side occupying half of the screen width:
import { Col, Row, Grid } from 'react-native-easy-grid';
const DisplayImages = ({pics}) => {
if (!pics || pics===[] || pics==={}) return null;
var len = pics.length,
if (len > 0) setImgAccordOpen(true);
switch(len) {
case 0:
return;
case 1: //<<<=== one image is uploaded
return (
<React.Fragment>
{displayImg(pics[0].path, screen_width*full, screen_width*full, 0)}
</React.Fragment>
);
case 2: //<<<=== case of 2 images uploaded
return (
<Grid style={{position:"absolute", paddingTop:0,paddingLeft:0}}>
<Row style={{paddingTop:0}}>
<Col style={{position:"absolute", top:0,left:0, paddingVertical:0}} > //<<==positioned at [0,0] on up left corner.
{displayImg(pics[0].path, screen_width*half, screen_width*half, 0)}
</Col>
<Col style={{position:"absolute", top:0,left:Math.ceil((screen_width-20)/2), paddingTop:0}}> //<<==positioned at [0, middle of screen]
{displayImg(pics[1].path, screen_width*half, screen_width*half, 1)}
</Col>
</Row>
</Grid>
);
.....
Here is the screen shot when one image is uploaded. The image is not positioned at absolute
Here is the screen shot when 2 images are uploaded. The first image is not within the viewing area and not visible at all.
How to position images right within viewing area?
The problem is the <ImageZoom> is not compatible with <FastImage> and only show a portion of image or positioning images way below the viewing screen. After removing <ImageZoom>, then the position of the images is back to normal as style dictated. Another module #dudigital/react-native-zoomable-view works fine with <FastImage>.
I'm creating a skills chart in a React component, where each bar starts with a short width and then it expands to a specified width after 0.5 second. The width is related to the skill level, defined in the following array:
const skills = [
{ skillName: 'JavaScript', level: 10, color: 'bca538' },
{ skillName: 'HTML', level: 9, color: 'af4336' },
{ skillName: 'CSS', level: 9, color: '2f81b7' },
]
The chart is represented in the following code:
<div className="chart__bars">
{skills.map((skill, index) => {
const { skillName, level, color } = skill
const { scale } = this.state
return (
<div
className={'chart__bars__item'}
key={skillName}
style={{
background: `#${color}`,
height: `${(100 / skills.length) * (index + 1)}%`,
width: `${scale ? level * 10 : 30}%`,
zIndex: skills.length - index,
}}
>
<h4
style={{
opacity: `${scale ? 1 : 0}`,
}}
>
{skillName}
</h4>
</div>
)
})}
</div>
After the component is mounted, it triggers a state change after 0.5 second, which should then expand each bar (logic for this is inside the style property in the code above). Here's the initial state:
state = {
scale: false,
}
And here's where I change it:
componentDidMount() {
setInterval(() => {
this.setState({ scale: true })
}, 500)
}
It works fine on the browser, but not on mobile. Using the devtools I can see the width being updated, but it won't expand. If I uncheck the tick box for the width, and then check it again, then the width expands (which should happen automatically).
The working example is on my website: https://marcelcruz.io.
Any thoughts would be much appreciated!
Thanks.
I am working on a react component and I need to limit the displayed text in a field that changes based on a input component. I am trying to make it so when the text box input becomes longer than the width of the component, to display what can fit followed by ... . I have something that works but it uses width: field to set how wide the text can go and I am looking for a responsive way to make it fit more or less text
<span
className='itemTitle'
onMouseLeave={this.handleMouseLeave}
id = 'itemTitle'
style = {{width: '420px', "whiteSpace": "nowrap",
overflow:"hidden !important",
'textOverflow': "ellipsis",
'display': 'inline-block'}}>
{prompt || card.get('promptText')}
</span>
The solution I came up with is as follows.
From the parent component which renders a Title Component, I add a ref and on add a resize eventlistener which sends new props to the ItemTitle component.
<div ref={input => {{this.rangeInput = input}}}>
<ItemTitle
prompt={prompt}
{...this.props}
width = {this.state.width}
/>
</div>
updateDimensions = () => {
this.setState({
width: this.rangeInput.offsetWidth
})
}
componentDidMount() {
this.updateDimensions();
window.addEventListener("resize", this.updateDimensions);
}
/**
* Remove event listener
*/
componentWillUnmount() {
window.removeEventListener("resize", this.updateDimensions);
}
Then in ItemTitle.js
<span
className='itemTitle'
onMouseLeave={this.handleMouseLeave}
id = 'itemTitle'
style = {{width: this.state.width - 140, "whiteSpace": "nowrap",
overflow:"hidden !important",
'textOverflow': "ellipsis",
'display': 'inline-block'}}>
{prompt || card.get('promptText')}
</span>