I am trying to create content slider in React, and to an extent I have succeeded, however I want the content to move horizontally instead of Vertically. Right now all the items are stacked on top of each other which I know is why they cannot work as a horizontal slider, initially I tried using flexbox in order to align the items next to each other but then they are unable to be moved using left or right in the CSS.
What I am currently doing is setting the top of the inner div (Which house the items) to a calculated number of pixels so that it will display the next "Slide". What I have been trying and failing is trying the get the items to be next to each other and the rather than top just change the left/ right of the slider to display the item.
(P.S. I have not added a way to stop the slider over the total number of items, will do that after)
Here is the React Code:
import React, { Component } from "react";
import "../App.css";
class Item extends Component {
constructor(props) {
super(props);
this.state = {
id: 1,
top: "0px",
};
}
onClickSlide = (_) => {
const item = this.state.id;
this.setState({ top: item * 200 * -1 + "px" });
this.setState({ id: item + 1 });
};
render() {
let dang = [
{ title: "Header 1", content: "Lorem ipsum proin gravida" },
{ title: "Header 2", content: "Lorem ipsum proin gravida" },
{ title: "Header 3", content: "Lorem ipsum proin gravida" },
{ title: "Header 4", content: "Lorem ipsum proin gravida" },
];
const item = this.state.id;
let innerStyle = {top: item * 200 * -1 + "px"};
return (
<div className="Outer">
<div className="inner" onClick={this.onClickSlide} style={{ top: this.state.top }}>
{dang.map((data, i) => {
return (
<div className="items">
<h3>{data.title}</h3>
<h3>{data.content}</h3>
</div>
);
})}
</div>
</div>
);
}
}
export default Item;
Here is the CSS:
.Outer {
display: block;
position: relative;
overflow: hidden;
width: 200px;
height: 200px;
}
.inner {
/* display: block; */
position: absolute;
transition: all 0.5s;
}
.items {
display: inline-block;
width: 200px;
height: 200px;
vertical-align: top;
}
Coolness, so with a bit of testing and some other carousel examples I have built my own version. There is still room for improvement but here is the base of it. the idea is to have a super long container were all of the images flex next to each other and by using the CSS property "translate" to move each image left or right depending on the direction clicked.
Below is the React JS example:
export default () => {
const [translate, setTranslate] = useState(0);
const handleClickBtn= (direction) => {
if (direction == "right") {
setTranslate({ translate: translate - 100 });
} else {
setTranslate({ translate: translate + 100 });
}
};
return (
<div className="banner-carousel">
<button
className="carousel-button btn-left"
onClick={(_) => handleClickBtn("left")}
>
Left
</button>
<div className="carousel-container">
<div
className="slide"
style={{ transform: `translateX(${translate}vw)`}}
>
<img className="carousel-image" src="https://www.publicdomainpictures.net/pictures/40000/nahled/random-texture.jpg" />
</div>
<div
className="slide"
style={{ transform: `translateX(${translate}vw)`}}
>
<img className="carousel-image" src="https://www.publicdomainpictures.net/pictures/40000/nahled/random-texture.jpg"/>
</div>
<div
className="slide"
style={{ transform: `translateX(${translate}vw)`}}
>
<img className="carousel-image" src="https://www.publicdomainpictures.net/pictures/40000/nahled/random-texture.jpg"/>
</div>
</div>
<button
className="carousel-button btn-right"
onClick={(_) => handleClickBtn("right")}
>
Right
</button>
</div>
);
};
Here is the SCSS:
.banner-carousel {
height: 720px;
.carousel-button {
cursor: pointer;
position: absolute;
top: 45%;
color: #fff;
z-index: 100;
&.btn-left {
left: 25px;
}
&.btn-right {
right: 25px;
}
}
.carousel-container {
display: flex;
width: 10000px;
.slide {
width: 100vw;
height: 720px;
transition: transform 0.25s linear;
.carousel-image {
width: 100%;
}
}
}
}
Again this still needs to be improved so might come back at a later date and update. If you have any other suggestions please feel free to post!
Related
In ios9, when this two condition is true: the flexbox parent's max-height property was set and the flexbox child's flex-basic property was 0%, the the flexbox child's height go 0。
It only happen in ios9(maybe less then ios9). I have tested the system above ios9, it didnot happen. that is so wired.
my test code:
js code:
import React, {useEffect} from 'react';
import './styles.less';
export const Page: React.FC = () => {
useEffect(() => {
alert(`${document.querySelector('#containerWrapper').offsetHeight}`);
}, []);
return (
<div id='componentPageBg' className="component-page-bg">
<div id='containerWrapper' className="container-wrapper">
<div className="div-wrapper">
<div className="div1">div1</div>
</div>
<div className="div-wrapper">
<div className="div2">div2</div>
</div>
<div className="div-wrapper">
<div className="div3">div3</div>
</div>
</div>
</div>
);
};
export default Page;
css code
.component-page-bg {
display: flex;
flex-direction: column;
// max-height: 900px;
}
.container-wrapper {
flex: 0 1 0%;
}
.div-wrapper {
}
.div1 {
background-color: red;
height: 50px;
}
.div2 {
background-color: green;
height: 50px;
}
.div3 {
background-color: gray;
height: 50px;
}
my test data:
I need to make the vertical slider animation ( dots and line ) as in this pic
i managed to do the Accordion and the dots but i don't know how i will going to implement it ( i'm using pseudo )
**my accordion component Where i define the logic of my nested accordions as in images based on array of data **
function MultiLevelAccordion({
data,
bodyClass,
headerClass,
wrapperClass,
renderHeader,
renderContent,
}) {
const RootAccordionId = 'parent-0';
const [accordionsStates, setActiveCardsIndex] = useMergeState({});
const onAccordionToggled = (id, activeEventKey) => {
console.log(activeEventKey);
setActiveCardsIndex({
[id]: activeEventKey ? Number(activeEventKey) : activeEventKey
});
};
console.log('data', data);
const accordionGenerator = (data, parentId) => {
return map(data, (item, index) => {
const active = accordionsStates[parentId] === index;
const hasChildren = item.hasOwnProperty('children') && isArray(item.children) && !isEmpty(item.children);
const isRootAccordion = RootAccordionId === parentId;
const isLastNestedAccordion = !isRootAccordion && !hasChildren;
const accordion = (
<Card className={classNames(wrapperClass, {
'nested-root-accordion': !isRootAccordion,
'last-nested-root-accordion': isLastNestedAccordion,
'multi-level-accordion': !isLastNestedAccordion
})}
>
<Accordion.Toggle
{...{ ...item.id && { id: item.id } }}
onClick={() => this}
as={Card.Header}
eventKey={`${index}`}
className={'cursor-pointer d-flex flex-column justify-content-center'}
>
<div className="d-flex justify-content-between align-items-center">
{renderHeader(item, hasChildren)}
<img
style={{
transition: 'all .5s ease-in-out',
transform: `rotate(${active ? 180 : 0}deg)`
}}
src={setIcon('arrow-down')}
className="ml-2"
alt="collapse"
/>
</div>
</Accordion.Toggle>
<Accordion.Collapse eventKey={`${index}`}>
<Card.Body
className={`accordion-content-wrapper ${!hasChildren ? 'accordion-children-body' : ''} ${bodyClass}`}
>
{!hasChildren ? renderContent(item, hasChildren) : (
<Accordion onSelect={activeEventKey => onAccordionToggled(`${parentId}-${index}`, activeEventKey)}>
<Fade cascade top when={active}>
{accordionGenerator(item.children, `${parentId}-${index}`)}
</Fade>
</Accordion>
)}
</Card.Body>
</Accordion.Collapse>
</Card>
);
return isRootAccordion ? accordion : (
<div className={'d-flex align-items-center'}>
{accordion}
<div className="accordion-indicator-wrapper">
<div className="accordion-indicator" id={`indicator-${parentId}-${index}`} />
</div>
</div>
);
});
};
if (!isArray(data)) {
return;
}
return (
<Accordion onSelect={activeEventKey => onAccordionToggled(RootAccordionId, activeEventKey)}>
{accordionGenerator(data, RootAccordionId)}
</Accordion>
);
}
export default MultiLevelAccordion;
the styles used in scss
.faqs-questions-wrapper {
padding: 20px 10px
}
.faqs-q-count {
color: $black-color;
font-size: calc(1rem - 1rem/8)
}
.faqs-q-a-wrapper {
flex-basis: 95%;
}
.faq-child-title {
color: $black-color
}
.nested-root-accordion {
flex-basis: 90%;
}
.accordion-indicator-wrapper {
flex-basis: 10%;
width: 100%;
display: flex;
justify-content: center;
.accordion-indicator {
width: 10px;
height: 10px;
border-radius: 50%;
background-color: $theme-color;
position: relative;
}
}
Any clue?
Thanks in Advance.
React JS is gonna make this easy
The lines expansion will need to be coded based on the height of the box window
For the dropdown container keep the vertical button lines in a separate div than the Accordian
Check out this pen for creating lines between buttons
https://codepen.io/cataldie/pen/ExVGjya
css part:
.status-container{
padding:10px;
margin:10px;
position:relative;
display: inline-block;
}
.bullet{
padding:0px;
margin:0px;
display: inline-block;
z-index: 10;
}
.bullet:before {
content: ' \25CF';
font-size: 5em;
}
.bullet-before{
/*position:relative;
right:-12px;*/
}
.bullet-after{
/*position:relative;
left:-30px;*/
}
.line{
stroke:blue;
stroke-width:0.3em;
padding:0px;
margin:0px;
display: inline-block;
}
.line-on{
stroke:red;
}
.line-off{
stroke:gray;
}
.color-on{
color: red;
}
.color-off{
color: gray;
}
https://codepen.io/emwing/pen/LgzJOx
I think you can use some inspiration here
I couldn't get a JSFiddle to work properly with React and some other dependencies, so I hope the link to this Github repo is sufficient for demonstrating the issue:
https://github.com/ishraqiyun77/button-issues/
Basically, a group of buttons is rendered and they should be auto-widened to fill white space and take up the whole row. This works in Chrome, Edge, Safari, and Firefox. It looks like this:
This isn't happening in IE. I've been messing with it for hours and haven't made much progress:
Here is the code, although could clone the repo I posted above:
// component.jsx
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import {
Button,
Col,
Modal,
ModalBody,
ModalHeader,
Row
} from 'reactstrap';
import styles from '../assets/scss/app.scss';
class TestPrint extends Component {
constructor(props) {
super(props);
this.state = {
modal: false,
}
this.toggle = this.toggle.bind(this);
}
toggle() {
this.setState({
modal: !this.state.modal
})
}
renderContent() {
let buttons = [];
for (let i = 1; i < 50; i++) {
buttons.push(
<Col key={i}>
<Button
key={i}
className='cuts-btn'
>
{i} - Test
</Button>
</Col>
);
};
return buttons;
}
render() {
return (
<div>
<Button
style={
{
position: 'fixed',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)'
}
}
onClick={this.toggle}
>
Open Modal for Buttons
</Button>
<Modal
size='lg'
isOpen={this.state.modal}
toggle={this.toggle}
className='results-modal'
>
<ModalHeader toggle={this.toggle}>
Button Issues
</ModalHeader>
<ModalBody>
<div className='results-bq-cuts'>
<Row>
{this.renderContent()}
</Row>
</div>
</ModalBody>
</Modal>
</div>
)
}
}
ReactDOM.render(<TestPrint />, document.getElementById('app'));
.results-modal {
max-width: 1200px;
.modal-content {
.modal-body {
margin-left: 13px;
margin-right: 13px;
.results-bq-cuts {
width: 100%;
.col {
padding:2px;
}
.cuts-btn {
font-size: 11px;
padding: 3px;
width: 100%;
box-shadow: none;
}
// .col {
// padding: 2px;
// display: table-cell;
// flex-basis: 100%;
// flex: 1;
// }
// .cuts-btn {
// font-size: 11px;
// padding: 3px;
// width: 100%;
// box-shadow: none;
// }
}
}
}
}
I have all of the <Button> wrapped in <Col> because that should be what is filling the white space by increasing the size of the button.
Thanks for the help!
IE11 doesn't like working out the width of flex items. If you add flex-basis: calc( 100% / 24 ); to .col it works :) Obviously use any width you want, but what I've given replicates the 21 boxes on one line. But essentially flex-basis needs a defined width to work.
Or add an extra class to each element (such as col-1 ) This'll also achieve the same thing.
I prepared example of image slider what I need.
I encounter with styling issue when using images with various dimensions. When element leaving the array, its location is set as absolute value, what is necessary for smooth transition, tho, but the image is also moved up.
I would like to have nice vertical align into middle even leave or enter the array, but could not get on any way.
Another issue, what I would like to solve is when left the window and then went back after a while. The animation running all cycles at once to reach current state instead just stop animation and run after. Maybe it is my responsibility, but browsers doesn't offer nice event to catch blur window or am I wrong?
According to this discussion
Thank you for any ideas.
let numbers = [{key:1},{key:2},{key:3},{key:4},{key:5},{key:6},{key:7}]
let images = [
{ key:1,
src:"http://lorempixel.com/50/100/sports/"},
{ key:2,
src:"http://lorempixel.com/50/50/sports/"},
{ key:3,
src:"http://lorempixel.com/100/50/sports/"},
{ key:4,
src:"http://lorempixel.com/20/30/sports/"},
{ key:5,
src:"http://lorempixel.com/80/20/sports/"},
{ key:6,
src:"http://lorempixel.com/20/80/sports/"},
{ key:7,
src:"http://lorempixel.com/100/100/sports/"}
]
new Vue({
el: '#rotator',
data: {
items: images,
lastKey: 7,
direction: false
},
mounted () {
setInterval(() => {
if (this.direction) { this.prevr() } else { this.nextr() }
}, 1000)
},
methods: {
nextr () {
let it = this.items.shift()
it.key = ++this.lastKey
this.items.push(it)
},
prevr () {
let it = this.items.pop()
it.key = ++this.lastKey
this.items.unshift(it)
}
}
})
.litem {
transition: all 1s;
display: inline-block;
margin-right: 10px;
border: 1px solid green;
background-color: lightgreen;
padding: 10px 10px 10px 10px;
height: 100px;
}
.innerDiv {
border: 1px solid red;
}
.container {
overflow: hidden;
white-space: nowrap;
height: 250px;
border: 1px solid blue;
background-color: lightblue;
}
.list-enter {
opacity: 0;
transform: translateX(40px);
}
.list-leave-to {
opacity: 0;
transform: translateX(-40px);
}
.list-leave-active {
position: absolute;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.11/vue.js"></script>
<div id="rotator">
<button #click="direction = !direction">
change direction
</button>
<transition-group
name="list"
tag="div"
class="container">
<div
v-for="item in items"
:key="item.key" class="litem">
<!--
<div
class='innerDiv'>
{{ item.key }}
</div>
-->
<div class='innerDiv'>
<img :src='item.src'>
</div>
</div>
</transition-group>
</div>
It tooks a while but on the end I think that I have got better result for sliding animation with changing direction feature.
One annoying think is when I swithing the sliding direction so animation is for a 'microsecond' changed to next state and than return to correct one, after it the animation continue as expected. It is happening only in one direction and I don't know how to fix it. Also last box behave differently too only once. No clue at all.
So just 98% solution :-)
let images = [
{key:1, domKey:1, src:"http://lorempixel.com/50/100/sports/" },
{key:2, domKey:2, src:"http://lorempixel.com/50/50/sports/" },
{key:3, domKey:3, src:"http://lorempixel.com/100/50/sports/" },
{key:4, domKey:4, src:"http://lorempixel.com/20/30/sports/" },
{key:5, domKey:5, src:"http://lorempixel.com/80/20/sports/" },
{key:6, domKey:6, src:"http://lorempixel.com/20/80/sports/" },
{key:7, domKey:7, src:"http://lorempixel.com/100/100/sports/" }
]
let setPositionRelative = el => el.style.position = "relative"
new Vue({
el: '#rotator',
data: {
items: images,
lastKey: 7,
direction: true,
changeDirectionRequest: false
},
mounted () {
Array.from(this.$el.querySelectorAll("div[data-key]")).map(setPositionRelative)
setInterval(() => {
if(this.changeDirectionRequest) {
this.changeDirectionRequest = false
this.direction = !this.direction
if (this.direction)
Array.from(this.$el.querySelectorAll("div[data-key]")).map(setPositionRelative)
else
Array.from(this.$el.querySelectorAll("div[data-key]")).map(el => el.style.position = "")
}
if (this.direction) this.prevr()
else this.nextr()
}, 1000)
},
methods: {
nextr () {
let it = this.items.shift()
it.key = ++this.lastKey
this.items.push(it)
},
prevr () {
let it = this.items.pop()
it.key = ++this.lastKey
this.items.unshift(it)
setPositionRelative(this.$el.querySelector("div[data-domkey='"+it.domKey+"']"))
}
}
})
.container {
overflow: hidden;
white-space: nowrap;
height: 200px;
border: 1px solid blue;
background-color: lightblue;
display: flex;
align-items: center;
}
.innerDiv {
border: 1px solid red;
width: auto;
height: auto;
display:-moz-box;
-moz-box-pack:center;
-moz-box-align:center;
display:-webkit-box;
-webkit-box-pack:center;
-webkit-box-align:center;
display:box;
box-pack:center;
box-align:center;
}
.litem {
transition: all 1s;
margin-right: 10px;
border: 1px solid green;
background-color: lightgreen;
padding: 10px 10px 10px 10px;
}
.list2-enter, .list-enter {
opacity: 0;
transform: translateX(40px);
}
.list2-leave-to, .list-leave-to {
opacity: 0;
transform: translateX(-40px);
}
.list-leave-active {
position: absolute;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.11/vue.js"></script>
<div id="rotator">
<button #click="changeDirectionRequest = true">change direction</button>
<transition-group name="list" tag="div" class="container">
<div v-for="item in items"
:key="item.key"
:data-domkey="item.domKey"
class="litem">
<div class='innerDiv'>
<img :src='item.src'>
</div>
</div>
</transition-group>
</div>
I'm developing a single page application / mobile app, with Vue.JS. I want a slide effect when changing the pages, and I can do it like this:
transition name="slide"
router-view transition
transition
But I wanted the reverse effect of the slide when the user returns the page. In other words, when the user opens a new page, the page will come from the right, but when they go back, the page will come from the left.
There is a plugin for Vue router, called vue-router-transition, but it does not work. It is out of date, it only works with very old versions of Vue.
Also there is a tutorial on dynamic transitions, but only works when it is parents routes, ex: example.com/rota1/rota2/rota3, which is not my case.
I thought of the following logic in the before.each.router, set the transition class (slide-left or slide-right) depending on whether the user clicked on a go back button.
The problem is that I do not know how to apply this logic in code. I would have to pass the value of a variable that is in main.js to app.vue and I do not know how to do this.
A while ago I've used the meta object in vue-router and added a "fake" depth, because I haven't any children objects. If you use children, then go with this example: https://github.com/vuejs/vue-router/blob/dev/examples/transitions/app.js
export default () => {
return [{
meta: {
depth: 0
},
path: '/home',
component: Home
},
{
meta: {
depth: 1
},
path: '/about',
component: About
}]
}
Now you can check it by your own like this in your App.vue.
watch: {
$route(to, from) {
const toDepth = to.meta.depth || 0;
const fromDepth = from.meta.depth || 0;
this.transitionName = toDepth >= fromDepth ? 'slide-left' : 'slide-right';
}
}
I see 2 options:
Store a variable in Vuex "transitionBack" and set it to true. Change the router-link to a #click method. In the method, first save the variable and then navigate to the link. Check that variable on your beforeRouteUpdate(to, from, next) method.
Implement some logic in your beforeRouteUpdate(to, from, next) method that will check on the name of the link (e.g. if you can only go "back" from a certain page, then keep a list of all these type of pages)
Hope this helps:
new Vue({
el: '#demo',
data: {
slideTransition: 'slide-left',
showChild: false,
},
watch: {
showChild(value) {
if (value) {
this.setSlideTransition('slide-right');
} else {
this.setSlideTransition('slide-left');
}
},
},
methods: {
setSlideTransition(slideDirection) {
// Note: 300ms mentioned below is matching with css transition timing
setTimeout(() => { this.slideTransition = slideDirection; }, 300);
},
},
});
body {
margin: 0;
color: #bdbdbd;
background-color: #161616;
font-family: Helvetica neue, roboto;
}
.container {
width: 500px;
background: #161616;
}
main {
width: 60%;
height: 300px;
background-color: #333;
}
aside {
width: 40%;
background-color: #555;
}
.container, main, .parent, .child, .content {
display: flex;
align-items: center;
justify-content: center;
}
.parent {
background-color: deepskyblue;
}
.child {
background-color: deeppink;
}
.container, main, aside {
position: relative;
height: 199px;
}
.pin {
top: 0;
left: 0;
right: 0;
bottom: 0;
position: absolute;
}
.pt-50 {
padding-top: 50px;
}
/* transitions */
.fade-enter-active,
.fade-leave-active {
transition-property: opacity;
transition-duration: 0.25s;
}
.fade-enter-active {
transition-delay: 0.25s;
}
.fade-enter,
.fade-leave-active {
opacity: 0;
}
.slide-left-leave-active,
.slide-left-enter-active,
.slide-right-leave-active,
.slide-right-enter-active {
transition: 0.3s;
}
.slide-left-enter {
transform: translate(100%, 0);
}
.slide-left-leave-to {
transform: translate(-100%, 0);
}
.slide-right-enter {
transform: translate(-100%, 0);
}
.slide-right-leave-to {
transform: translate(100%, 0);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo" class="container">
<aside>
<transition name="fade">
<div class="pin parent" v-if="!showChild">
<div>
<h1>Parent</h1>
Go To Child
</div>
</div>
</transition>
<transition :name="slideTransition">
<div class="pin child" v-if="showChild">
<div>
<h1>Child</h1>
Go To Parent
</div>
</div>
</transition>
</aside>
<main>
<div>
<h1>Main</h1>
<transition name="fade">
<div v-if="showChild" class="pin content pt-50" key="child">
<h4>Child Content here</h4>
</div>
<div v-else="" class="pin content pt-50" key="parent">
<h4>Parent Content here</h4>
</div>
</transition>
</div>
</main>
</div>