React Native Card Carousel view? - javascript

Does anyone know how can we achieve this kind of view in React Native, or is there any available components out there that can help on this issue?
I've seen in F8 2016 app too, been searching on how to achieve the transition and the carousel-like view with horizontal scrolling.

I know that the question is old, but a co-worker and I recently had to create a component that answers this particular need. We ended up open-sourcing it, so it's all yours to try: react-native-snap-carousel.
The plugin is now built on top of FlatList (versions >= 3.0.0), which is great to handle huge numbers of items. It also provides previews (the effect you were after), snapping effect, parallax images, RTL support, and more.
You can take a look at the showcase to get a grasp of what can be achieved with it. Do not hesitate to share your experience with the plugin since we're always trying to improve it.
Edit : two new layouts have been introduced in version 3.6.0 (one with a stack of cards effect and the other with a tinder-like effect). Enjoy!

You can achieve this using ScrollView with paging enabled on iOS and ViewPagerAndroid on Android.
F8 being an open source app,
you can see that's what it's actually using:
https://github.com/fbsamples/f8app/blob/master/js/common/ViewPager.js
This component renders all pages.
If you only want to have the visible and left and right pages rendered to save memory, there's another component built on top of it that does it:
https://github.com/fbsamples/f8app/blob/master/js/common/Carousel.js
There are various other similar implementations available:
https://js.coach/react-native?search=carousel
https://js.coach/react-native?search=swiper
However I'm not recommending https://github.com/leecade/react-native-swiper as I've had several issues with it.

Speaking about the swiper-component claiming the best of the world, it still does not work out of the box (as of November 2018) as described in the official swiper-react-native documentation. The issue and a workaround is described in the swiper issue 444:
The error message (on Android) states console.error: "fontFamily 'Arial' is not a system font and has not been loaded through Exponent.Font.loadAsync.
Zach Dixon provided an elegant quick-fix which I repeat here for everybody's convenience. Simply use the following JSX-snippet inside your render()-function to avoid that a new font is required:
<Swiper style={styles.wrapper} showsButtons={true}
nextButton={<Text>></Text>} prevButton={<Text><</Text>}>
<View style={styles.slide1}><Text style>Slide 1</Text></View>
<View style={styles.slide2}><Text style>Slide 2</Text></View>
<View style={styles.slide3}><Text style>Slide 3</Text></View>
</Swiper>
For those interested in explanations on how to implement carousel with Scroll-View only, I recommend a tutorial on a simple image carousel with ScrollView. The tutorial is straight forward and elaborates on the things one has to take care of, but you cannot use it out of the box within or on top of other View-elements. In particular the snapping does not work to well (on Android).

You can create your own custom carousel. The Carousel end result looks like this-
goToNextPage = () => {
const childlenth = this.getCustomData().length;
selectedIndex = selectedIndex + 1;
this.clearTimer();
if (selectedIndex === childlenth) {
this.scrollRef.current.scrollTo({ offset: 0, animated: false, nofix: true });
selectedIndex = 1;
}
this.scrollRef.current.scrollTo({
animated: true,
x: this.props.childWidth * selectedIndex,
});
this.setUpTimer();
}
// pushing 1st element at last
getCustomData() {
const {data} = this.props;
const finaldata = [];
finaldata.push(...data);
finaldata.push(data[0]);
return finaldata;
}
This is the main logic used behind looped carousel.
Here we are pushing the first item at last in the list again and then when scroll reaches at last position we are making the scrollview to scroll to first position as first and last element are same now and we scroll to first position with animation like this
this.scrollRef.current.scrollTo({ offset: 0, animated: false, nofix: true });
For further reference go through the link provided.
https://goel-mohit56.medium.com/custom-horizontal-auto-scroll-looped-carousel-using-scrollview-42baa5262f95

Related

Scroll to top of a bubble in botframework webchat

Some answers of our chatbot are very long. The webchat scrolls automatically to the bottom so users have to scroll up to get to the top of the bubble and start reading.
I've implemented a custom renderer (react) to wrap the answers into a custom component which simply wraps the answer into a div-tag. I also implemented a simple piece of code to scroll to the top of the bubble.
const MyCustomActivityContainer = ({ children }) => {
const triggerScrollTo = () => {
if (scrollRef && scrollRef.current) {
(scrollRef.current as any).scrollIntoView({
behavior: 'smooth',
block: 'start',
})
}
}
const scrollRef: React.RefObject<HTMLDivElement> = React.createRef()
return (
<div ref={ scrollRef } onClick={ triggerScrollTo }>
{ children }
</div>
)
}
export const activityMiddleware = () => next => card => {
if (/* some conditions */) {
return (
<MyCustomActivityContainer>
{ next(card) }
</MyCustomActivityContainer>
);
} else {
return (
{ next(card) }
)
}
};
But this only works if the scrollbar slider is not at its lowest position (there is at least 1 pixel left to scroll down, see here). The problem is the useScrollToBottom hook which always scrolls to bottom automatically if the scrollbar is completely scrolled down.
Is there any way to overwrite the scroll behavior or to temporarily disable the scrollToBottom feature?
As there is no reproducible example I can only guess.
And I'll have to make some guesses on the question too.
Because it's not clear what exactly in not working:
Do you mean that click on the <div> of MyCustomActivityContainer and subsequent call to triggerScrollTo doesn't result into a scroll?
That would be strange, but who knows. In this case I doubt anyone will help you without reproducible example.
Or do you mean that you can scroll the message into view, but if it is already in the view then new messages can result into a scroll while user is still reading a message.
That's so, but it contradicts with you statement that your messages are very long, because that would be the problem with short messages, not with the long ones.
But anyway, you should be able to fix that.
If it works fine with 1 pixel off the lowest position, then just scroll that 1 pixel. You'll need to find the scrollable element. And do scrollable_element.scrollTop -= 1. I tested this approach here. And it worked (there the scrollable element is the grandparent of <p>'s)
Or do you try to scroll automatically at the moment the message arrives? Аnd that is the real issue, but you forgot to mention it, and didn't posted the code that tries to auto-scroll?
In that case you can try to use setTimeout() and defer the scroll by, let's say, 200ms.
This number in based on what I gathered from the source:
BotFramework-WebChat uses react-scroll-to-bottom
In react-scroll-to-bottom there are some timeouts 100ms and 34ms
BotFramework-WebChat doesn't redefine them
There are some heuristics in react-scroll-to-bottom that probably coursing the trouble
https://github.com/compulim/react-scroll-to-bottom/blob/3eb21bc469ee5f5095a431ac584be29a0d2da950/packages/component/src/ScrollToBottom/Composer.js
Currently, there are no reliable way to check if the "scroll" event is trigger due to user gesture, programmatic scrolling, or Chrome-synthesized "scroll" event to compensate size change. Thus, we use our best-effort to guess if it is triggered by user gesture, and disable sticky if it is heading towards the start direction.
And
https://github.com/compulim/react-scroll-to-bottom/blob/f19b14d6db63dcb07ffa45b4433e72284a9d53b6/packages/component/src/ScrollToBottom/Composer.js#L91
For what we observed, #1 is fired about 20ms before #2. There is a chance that this stickyCheckTimeout is being scheduled between 1 and 2. That means, if we just look at #1 to decide if we should scroll, we will always scroll, in oppose to the user's intention.
That's why I think you should use setTimeout()
Since there isn't a reproducible code for me tweak and show you. My suggestion is tweak your code slightly. Chatbot requires constant streaming of data when a new message arrives calculate the height of the div element created for the message. If the div element is greater than the widget height scroll to the top else you can choose to leave it as it is.

How to get Xterm.js resize properly?

tl;dr
I've created a React wrapper to render an array of log messages into a terminal but resizing is giving a weird output (see screenshot). (There is a React-Wrapper on NPM but that wasn't working for my use-case - caused screen flickering)
I'm working on a feature for Guppy where I'm adding Xterm.js for the terminal output.
The PR can be found here.
I've added xterm because of hyperlink scanning/parsing and that is working.
But I'm stuck with getting resize to work. If I'm starting the devServer in the app and wait for some text it will display with correct letter width.
If I reduce the size I'm getting an output with an incorrect letter width.
Like in the following screenshot:
It is always looking correct in the not resized state but after resize it will get the wrong display - so this will happen for enlarging & shrinking the screen width.
The output should look similar to the following screenshot (maybe with some wrapped lines):
I think this is caused by Fit addon or the way I'm handling resizing with the resize observer but I'm not sure.
The span style of xterm letter are getting a width of NaNpx like in the following screenshot:
Is this caused by a media query I'm using? I haven't seen that yet maybe I have to temporarily disable all media queries to see if that's causing the behaviour.
What I have tried so far:
Wrapped this.xterm.fit() into a setTimeout(func, 0) but with-out an effect
Modified some of the styles I'm using but I haven't found the cause.
Code
The code I'm using can be found on Github branch feature-terminal-links but here I'd like to extract the parts I've added to get Xterm to work with React:
I created a styled-component XtermContainer as a div so I can add Xterm styles and own styling. The following code is inside render and will be our xterm.js container (innerRef will be used later in ComponentDidMount to intialize Xterm with that container):
<XtermContainer
width={width}
height={height}
innerRef={node => (this.node = node)}
/>
Init xterm in componentDidMount with the container above:
componentDidMount() {
Terminal.applyAddon(webLinks);
Terminal.applyAddon(localLinks);
Terminal.applyAddon(fit);
this.xterm = new Terminal({
convertEol: true,
fontFamily: `'Fira Mono', monospace`,
fontSize: 15,
rendererType: 'dom', // default is canvas
});
this.xterm.setOption('theme', {
background: COLORS.blue[900],
foreground: COLORS.white,
});
this.xterm.open(this.node);
this.xterm.fit();
/* ... some addon setup code here (not relevant for the problem) ... */
}
Added react-resize-observer inside of the wrapper that is also containing the terminal container so I can trigger this.xterm.fit() if the size changes (in the repo there is a setTimeout wrapper around for testing).
<ResizeObserver onResize={() => this.xterm && this.xterm.fit()} />
Using componentDidUpdate(prevProps, prevState) to update the terminal and scroll the terminal to the bottom if the component is getting new logs:
componentDidUpdate(prevProps, prevState) {
if (prevProps.task.logs !== this.state.logs) {
if (this.state.logs.length === 0) {
this.xterm.clear();
}
for (const log of this.state.logs) {
/*
We need to track what we have added to xterm - feels hacky but it's working.
`this.xterm.clear()` and re-render everything caused screen flicker that's why I decided to not use it.
Todo: Check if there is a react-xterm wrapper that is not using xterm.clear or
create a wrapper component that can render the logs array (with-out flicker).
*/
if (!this.renderedLogs[log.id]) {
this.writeln(log.text);
this.xterm.scrollToBottom();
this.renderedLogs[log.id] = true;
}
}
}
}
Ideas I have to find the cause:
Check ResizeObserver code. (see update below)
Try to find why xterm css is getting a NaN width. Is Xterm.js using the style width of the container? If yes, maybe that's not correctly set.
Update
OK, the resize obeserver is probably not needed as I'm getting the same behaviour after commenting out the <ResizeObserver/> in render. So I think it's caused by xterm.js or the css in Guppy.
I have a fix for the issue. It's now working in the above mentioned feature branch. Not sure if there is a better solution but it's working for me.
I like to explain how I have fixed the resizing issue:
The problem was the OnlyOn component that was used in DevelopmentServerPane. It always rendered two TerminalOutput components. One terminal was hidden with display: none and the other was displayed with display: inline - the style change was handled with a media query inside a styled-component.
After replacing OnlyOn with React-responsive and using the render props to check mdMin breakpoint it was working as expected. React-responsive is removing the not displayed mediaquery component from DOM so only one terminal in DOM at the same time.
I still don't know why there was a problem with the letter width but probably the two instances collided somehow. I couldn't create a minimal reproduction. I tried to recreate the issue in this Codesandbox but I have only resized one Terminal at a time and so I haven't got the issue there.
The code that fixed the problem (simplified version from the above mentioned repo):
import MediaQuery from 'react-responsive';
const BREAKPOINT_SIZES = {
sm: 900,
};
const BREAKPOINTS = {
mdMin: `(min-width: ${BREAKPOINT_SIZES.sm + 1}px)`,
};
const DevelopmentServerPane = () => (
<MediaQuery query={BREAKPOINTS['mdMin']}>
{matches =>
matches ? (
<div>{/* ... render Terminal for matching mdMin and above */}</div>
) : (
<div> {/* ... render Terminal for small screens */}</div>
)
}
</MediaQuery>
);

How to rebuild the iOS Picker animation in React Native

Is there a way in React Native to rebuild the iOS picker component completely in Java Script? I don't need the common picker, but a normal scroll view with a similar fade-out effect like the iOS picker.
EDIT – I think I have not explained my initial answer exactly enough. This is why I complete it here:
I want to build a scroll view that takes over the whole screen. It's not supposed to give the user the possibility to elect some item, like the the iOS Picker does. Nevertheless, it's supposed to be a 'normal' scroll view, that shows the user some information, e.g. different chats, tasks, news and so on.
The only difference to React Native's common scroll view should be the fade-out effect at the top: When the user scrolls the content up, it should not just leave the screen at its top edge, but it should use the iOS Picker's fade-out effect (see picture).
This fade-out effect is made up of two parts: First of all, it raises the content's transparency with a decreasing y-coordinate. Furthermore, this content seems to escape into the third dimension.
My problem is, that I don't see a way to achieve this three-dimensionality of the content in React Native. I've to add, that the content in my scroll view does not consist of small, equally sized items (like e.g. the texts 'Item 1', 'Item 2', 'Item 3',...), but of bigger items with different sizes like images or whole textboxes.
You can use this NPM module to get what you want. That module works the same in Android and iOS. Do not reinvent the wheel :)
EDIT: Now I've understood what you want. You can try this snack that I've made for you:
https://snack.expo.io/r1qnxSt9m
Of course you need to improve it, but it's a beginning.
You can achieve the desired effect with the Animated api. The idea is to set different input ranges to the items in your list. You then hook the opacity to the scroll value of your ScrollView (or any list component). I have simplified the code, but it should be enough to demonstrate the idea.
The example below only demonstrates an opacity effect, but you could easily add a translate effect to get the exact animation that you are looking for.
const data = []; // array that contains the text
const items = [];
for (let i = 0; i < data.length; ++i) {
const distanceFromViewCenter = Math.abs(i * ITEM_HEIGHT);
const inputRange = [
-distanceFromViewCenter - 2 * ITEM_HEIGHT,
-distanceFromViewCenter - ITEM_HEIGHT,
-distanceFromViewCenter, // Middle of picker
-distanceFromViewCenter + ITEM_HEIGHT,
-distanceFromViewCenter + 2 * ITEM_HEIGHT,
];
items.push(
<Animated.View
style={{
opacity: this._scrollValue.interpolate({
inputRange,
outputRange: [0.0, 0.3, 1.0, 0.3, 0.0],
}),
}}
>
<Text style={{ height: ITEM_HEIGHT }}>{data[i]}</Text>
</Animated.View>
)
}
<ScrollView
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: this._scrollValue } } }],
{ useNativeDriver: true }
)}
>
{items}
</ScrollView>

JS (No Jquery) - Change CSS when scrolled to a set point

Sorry, but I am a complete noob with JS. I am using Bootstrap to try build my first website.
The website has a fixed top navbar. I want to change the navbar's border-bottom properties when it reaches the bottom of the header div (about 480/500px down the page).
Currently the border-bottom is white, but I want to change it to blue when scrolled beyond a certain point (bottom of header) and then change back to white if scrolled back up again. The effect I want is the appearance of the fixed nav 'picking up' the bottom border of the banner section when it scroll's past.
I have given the navbar div an id of id="n1", and created a class .navbar1{border-bottom: 1px solid rgba(46,152,255,1)!Important;} to add to override the existing css.
I am not using jQuery because I don't use much JS and I don't want to call it just for a few things - it is a big file. I have tried various things without any success. Probably because they relied on jQuery? I don't know. For example, the last one was:
$(window).scroll( function(){
if($(window).scrollTop() > 50) $("n1").addClass("navbar1");
else $("n1").removeClass("navbar1");
});
Anyway, I was hoping someone may be able to help me with the plain/pure JS to change the attribute properties as described. Thank you in advance for any assistance.
EDIT:
This has been kindly answered below. But given some comments, I thought it might be useful to clarify my use of JS: My website requires very little JS functionality so I have chosen to inline my JS, rather than call an external JS file or files - such as jquery.js and bootstrap.js which are relatively large files.
Although I lose the benefit of caching the JS, and my HTML is slightly larger, I am happy to do that because in my case I feel those losses are more than made up for the increased initial page load speed from:
not having to make additional http requests,
not having to load relatively large files.
It is certainly not for everyone, but I feel that it suits my case. Having said that, when all is done and my website is up and running I will probably do some testing to see whether a custom external JS file is better again. Basically, I am only using Bootstrap for its CSS functionality, not its JS functionality. I hope that makes sense.
This demo may help you!
It doesn't use jQuery.
Here is the javascript code:
window.onscroll = function() {
var nav = document.getElementById('nav');
if ( window.pageYOffset > 100 ) {
nav.classList.add("navbar1");
} else {
nav.classList.remove("navbar1");
}
}
I did a small change on #radonirina-maminiaina amazing answer.
While it works, I do prefer avoiding doing unnecessary DOM calls during the onScroll event. The onScroll event can be triggered quite often on some devices, so it's best to keep its handler as fast as possible.
In my solution, I cache the nav DOM element on a closure and I only update its classes if the offset changes.
window.onscroll = function () {
let isScrolled = false
const scrollPoint = 100
const nav = document.getElementById('navbar')
function onScroll () {
if ( window.pageYOffset > scrollPoint && !isScrolled ) {
nav.classList.add("scroll");
isScrolled = true
} else if (window.pageYOffset <= scrollPoint && isScrolled) {
nav.classList.remove("scroll");
isScrolled = false
}
}
onScroll() // Makes sure that the class is attached on the first render
return onScroll
}()

Mac Safari 7 Rendering Issue scrollLeft() issue

I created a dynamic table that scrolls left and right, has resizable columns, has a fixed header, etc. This table works great on EVERY browser I've tried. Even IE8 looks good (missing features, but still good).
This issue arises when I try to view the table in Safari 7.0.4 on my Macbook.
Attached is what is should look like (the fixed header is on the bottom for demonstration purposes):
when you scroll, the fixed header, body, and fixed scrollbar all are connected via some jQuery scrollLeft() functions (scroll one, scroll all):
var tableHeaderSpace = $('.table-full-wrap-space'),
tableHeader = $('.table-full-wrap-header'),
tableBody = $('.table-full-wrap-body'),
tableScroll = $('.table-full-wrap-scroll');
tableScroll.bind('scroll', function() {
tableHeader.scrollLeft(tableScroll.scrollLeft());
tableBody.scrollLeft(tableScroll.scrollLeft());
});
tableHeader.bind('scroll', function() {
tableScroll.scrollLeft(tableHeader.scrollLeft());
tableBody.scrollLeft(tableHeader.scrollLeft());
});
tableBody.bind('scroll', function() {
tableScroll.scrollLeft(tableBody.scrollLeft());
tableHeader.scrollLeft(tableBody.scrollLeft());
});
$(window).bind("scroll", function() {
var tableHeaderOffset = tableHeaderSpace.offset().top;
if (this.pageYOffset >= tableHeaderOffset) {
tableHeader.addClass('isFixed');
} else {
tableHeader.removeClass('isFixed');
}
});
Again, this works great...but as you scroll right a bit more, the browser starts duplicating content within that fixed header:
The issue is is that no 'actual' content is being duplicated - this is some sort of browser fragmenting that is showing duplicates - without adding elements in the DOM.
The next picture is the browser doing some more "magic". at certain points in horizontal scrolling, the whole fixed header's colors gets inverted:
I wasn't able to get a snapshot of it, but it also once duplicated the "record count" bar below it.
Anyone have any ideas what's going on here? I tried to duplicate this in jsFiddle but no dice. From that, I would assume that this is an issue with my code, but the results are only with ONE specific browser on mac (safari), and it is doing some STRANGE stuff.
Last note - since I can't replicate this in jsFiddle, i'm not sure how I could report this to Apple (the working (or 'broken') example is proprietary and I can't give out access to it).
EDIT:
here's the jsfiddle where I tried to duplicate the issue (very rough - but it's functional):
jsFiddle Duplication Attempt
so - I knew this wouldn't be a hot topic question, but I thought I would still give it a go ahead.
as for the answer, I found some old table css that was overlapping my new stuff - which in turn was somehow flipping safari out so bad that it was fragmenting it.
previous old code: background: transparent;
new code: background: #fff;
This doesn't make sense to me - but until someone else comes up with an hypothesis, I'll mark this as the answer.
now my number-one contender for worst browser: safari - look out, IE.

Categories

Resources