mirror of
https://github.com/zhigang1992/react-native-web.git
synced 2026-04-27 21:07:26 +08:00
[add] initial ScrollView
Supports the following props: `children`, `contentContainerStyle`, `horizontal`, `onScroll`, `scrollEnabled`, `scrollEventThrottle`, and `style`. Fix #6
This commit is contained in:
committed by
Nicolas Gallagher
parent
a1664927ce
commit
894fd0362d
@@ -1,6 +1,7 @@
|
|||||||
# ScrollView
|
# ScrollView
|
||||||
|
|
||||||
TODO
|
Scrollable `View` for use with bounded height, either by setting the height of
|
||||||
|
the view directly (discouraged) or by bounding the height of ancestor views.
|
||||||
|
|
||||||
## Props
|
## Props
|
||||||
|
|
||||||
@@ -11,27 +12,29 @@ Child content.
|
|||||||
**contentContainerStyle**: style
|
**contentContainerStyle**: style
|
||||||
|
|
||||||
These styles will be applied to the scroll view content container which wraps
|
These styles will be applied to the scroll view content container which wraps
|
||||||
all of the child views. Example:
|
all of the child views.
|
||||||
|
|
||||||
**horizontal**: bool = false
|
**horizontal**: bool = false
|
||||||
|
|
||||||
When true, the scroll view's children are arranged horizontally in a row instead of vertically in a column. Default: `false`.
|
When true, the scroll view's children are arranged horizontally in a row
|
||||||
|
instead of vertically in a column.
|
||||||
|
|
||||||
**onScroll**: function
|
**onScroll**: function
|
||||||
|
|
||||||
Fires at most once per frame during scrolling. The frequency of the events can be contolled using the `scrollEventThrottle` prop.
|
Fires at most once per frame during scrolling. The frequency of the events can
|
||||||
|
be contolled using the `scrollEventThrottle` prop.
|
||||||
|
|
||||||
**scrollEnabled**: bool
|
**scrollEnabled**: bool = true
|
||||||
|
|
||||||
When false, the content does not scroll. Default: `true`.
|
When false, the content does not scroll.
|
||||||
|
|
||||||
**scrollEventThrottle**: number
|
**scrollEventThrottle**: number = 0
|
||||||
|
|
||||||
This controls how often the scroll event will be fired while scrolling (in
|
This controls how often the scroll event will be fired while scrolling (in
|
||||||
events per seconds). A higher number yields better accuracy for code that is
|
events per seconds). A higher number yields better accuracy for code that is
|
||||||
tracking the scroll position, but can lead to scroll performance problems.
|
tracking the scroll position, but can lead to scroll performance problems. The
|
||||||
Default: `0` (the scroll event will be sent only once each time the view is
|
default value is `0`, which means the scroll event will be sent only once each
|
||||||
scrolled.)
|
time the view is scrolled.
|
||||||
|
|
||||||
**style**: style
|
**style**: style
|
||||||
|
|
||||||
@@ -40,20 +43,42 @@ scrolled.)
|
|||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import React, { ScrollView } from 'react-native-web'
|
import React, { ScrollView, StyleSheet } from 'react-native-web'
|
||||||
|
|
||||||
const { Component, PropTypes } = React;
|
import Item from './Item'
|
||||||
|
|
||||||
class Example extends Component {
|
export default class App extends React.Component {
|
||||||
static propTypes = {
|
constructor(props, context) {
|
||||||
|
super(props, context)
|
||||||
|
this.state = {
|
||||||
|
items: Array.from({ length: 20 }).map((_, i) => ({ id: i }))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static defaultProps = {
|
onScroll(e) {
|
||||||
|
console.log(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
|
<ScrollView
|
||||||
|
children={this.state.items.map((item) => <Item {...item} />)}
|
||||||
|
contentContainerStyle={styles.container}
|
||||||
|
horizontal
|
||||||
|
onScroll={(e) => this.onScroll(e)}
|
||||||
|
scrollEventThrottle={60}
|
||||||
|
style={styles.root}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
root: {
|
||||||
|
borderWidth: '1px'
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
padding: '10px'
|
||||||
|
}
|
||||||
|
})
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import GridView from './GridView'
|
import GridView from './GridView'
|
||||||
import Heading from './Heading'
|
import Heading from './Heading'
|
||||||
import MediaQueryWidget from './MediaQueryWidget'
|
import MediaQueryWidget from './MediaQueryWidget'
|
||||||
import React, { Image, StyleSheet, Text, TextInput, Touchable, View } from '../../src'
|
import React, { Image, StyleSheet, ScrollView, Text, TextInput, Touchable, View } from '../../src'
|
||||||
|
|
||||||
export default class App extends React.Component {
|
export default class App extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@@ -9,6 +9,13 @@ export default class App extends React.Component {
|
|||||||
style: View.propTypes.style
|
style: View.propTypes.style
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constructor(...args) {
|
||||||
|
super(...args)
|
||||||
|
this.state = {
|
||||||
|
scrollEnabled: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { mediaQuery } = this.props
|
const { mediaQuery } = this.props
|
||||||
const rootStyles = {
|
const rootStyles = {
|
||||||
@@ -152,6 +159,52 @@ export default class App extends React.Component {
|
|||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</GridView>
|
</GridView>
|
||||||
|
|
||||||
|
<Heading size='large'>ScrollView</Heading>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
checked={this.state.scrollEnabled}
|
||||||
|
onChange={() => this.setState({
|
||||||
|
scrollEnabled: !this.state.scrollEnabled
|
||||||
|
})}
|
||||||
|
type='checkbox'
|
||||||
|
/> Enable scroll
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<Heading>Default layout</Heading>
|
||||||
|
<View style={styles.scrollViewContainer}>
|
||||||
|
<ScrollView
|
||||||
|
contentContainerStyle={styles.scrollViewContentContainerStyle}
|
||||||
|
onScroll={e => console.log('ScrollView.onScroll', e)}
|
||||||
|
scrollEnabled={this.state.scrollEnabled}
|
||||||
|
scrollEventThrottle={1} // 1 event per second
|
||||||
|
style={styles.scrollViewStyle}
|
||||||
|
>
|
||||||
|
{Array.from({ length: 50 }).map((item, i) => (
|
||||||
|
<View key={i} style={styles.box}>
|
||||||
|
<Text>{i}</Text>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Heading>Horizontal layout</Heading>
|
||||||
|
<View style={styles.scrollViewContainer}>
|
||||||
|
<ScrollView
|
||||||
|
contentContainerStyle={styles.scrollViewContentContainerStyle}
|
||||||
|
horizontal
|
||||||
|
onScroll={e => console.log('ScrollView.onScroll', e)}
|
||||||
|
scrollEnabled={this.state.scrollEnabled}
|
||||||
|
scrollEventThrottle={1} // 1 event per second
|
||||||
|
style={styles.scrollViewStyle}
|
||||||
|
>
|
||||||
|
{Array.from({ length: 50 }).map((item, i) => (
|
||||||
|
<View key={i} style={{...styles.box, ...styles.horizontalBox}}>
|
||||||
|
<Text>{i}</Text>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -179,6 +232,9 @@ const styles = StyleSheet.create({
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
borderWidth: '1px'
|
borderWidth: '1px'
|
||||||
},
|
},
|
||||||
|
horizontalBox: {
|
||||||
|
width: '50px'
|
||||||
|
},
|
||||||
boxFull: {
|
boxFull: {
|
||||||
width: '100%'
|
width: '100%'
|
||||||
},
|
},
|
||||||
@@ -194,5 +250,14 @@ const styles = StyleSheet.create({
|
|||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
height: '200px',
|
height: '200px',
|
||||||
justifyContent: 'center'
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
|
scrollViewContainer: {
|
||||||
|
height: '200px'
|
||||||
|
},
|
||||||
|
scrollViewStyle: {
|
||||||
|
borderWidth: '1px'
|
||||||
|
},
|
||||||
|
scrollViewContentContainerStyle: {
|
||||||
|
padding: '10px'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"inline-style-prefixer": "^0.3.3",
|
"inline-style-prefixer": "^0.3.3",
|
||||||
|
"lodash.debounce": "^3.1.1",
|
||||||
"react-tappable": "^0.7.1",
|
"react-tappable": "^0.7.1",
|
||||||
"react-textarea-autosize": "^3.0.0"
|
"react-textarea-autosize": "^3.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
4
src/components/ScrollView/ScrollViewStylePropTypes.js
Normal file
4
src/components/ScrollView/ScrollViewStylePropTypes.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import View from '../View'
|
||||||
|
export default {
|
||||||
|
...(View.stylePropTypes)
|
||||||
|
}
|
||||||
@@ -1 +1,11 @@
|
|||||||
/* eslint-env mocha */
|
/* eslint-env mocha */
|
||||||
|
|
||||||
|
import * as utils from '../../../modules/specHelpers'
|
||||||
|
|
||||||
|
import ScrollView from '../'
|
||||||
|
|
||||||
|
suite('components/ScrollView', () => {
|
||||||
|
test('prop "style"', () => {
|
||||||
|
utils.assertProps.style(ScrollView)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@@ -1,18 +1,138 @@
|
|||||||
|
import { pickProps } from '../../modules/filterObjectProps'
|
||||||
|
import debounce from 'lodash.debounce'
|
||||||
import React, { PropTypes } from 'react'
|
import React, { PropTypes } from 'react'
|
||||||
|
import ScrollViewStylePropTypes from './ScrollViewStylePropTypes'
|
||||||
|
import StyleSheet from '../../modules/StyleSheet'
|
||||||
import View from '../View'
|
import View from '../View'
|
||||||
|
|
||||||
|
const scrollViewStyleKeys = Object.keys(ScrollViewStylePropTypes)
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
initial: {
|
||||||
|
flexGrow: 1,
|
||||||
|
flexShrink: 1,
|
||||||
|
overflow: 'scroll'
|
||||||
|
},
|
||||||
|
initialContentContainer: {
|
||||||
|
flexGrow: 1,
|
||||||
|
flexShrink: 1
|
||||||
|
},
|
||||||
|
row: {
|
||||||
|
flexDirection: 'row'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
class ScrollView extends React.Component {
|
class ScrollView extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
children: PropTypes.any
|
children: PropTypes.any,
|
||||||
|
contentContainerStyle: PropTypes.shape(ScrollViewStylePropTypes),
|
||||||
|
horizontal: PropTypes.bool,
|
||||||
|
onScroll: PropTypes.func,
|
||||||
|
scrollEnabled: PropTypes.bool,
|
||||||
|
scrollEventThrottle: PropTypes.number,
|
||||||
|
style: PropTypes.shape(ScrollViewStylePropTypes)
|
||||||
}
|
}
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
className: ''
|
contentContainerStyle: styles.initialContentContainer,
|
||||||
|
horizontal: false,
|
||||||
|
scrollEnabled: true,
|
||||||
|
scrollEventThrottle: 0,
|
||||||
|
style: styles.initial
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(...args) {
|
||||||
|
super(...args)
|
||||||
|
this._debouncedOnScrollEnd = debounce(this._onScrollEnd, 100)
|
||||||
|
this.state = {
|
||||||
|
isScrolling: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_onScroll(e) {
|
||||||
|
const { scrollEventThrottle } = this.props
|
||||||
|
const { isScrolling, scrollLastTick } = this.state
|
||||||
|
|
||||||
|
// A scroll happened, so the scroll bumps the debounce.
|
||||||
|
this._debouncedOnScrollEnd(e)
|
||||||
|
|
||||||
|
if (isScrolling) {
|
||||||
|
// Scroll last tick may have changed, check if we need to notify
|
||||||
|
if (this._shouldEmitScrollEvent(scrollLastTick, scrollEventThrottle)) {
|
||||||
|
this._onScrollTick(e)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Weren't scrolling, so we must have just started
|
||||||
|
this._onScrollStart(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_onScrollStart() {
|
||||||
|
this.setState({
|
||||||
|
isScrolling: true,
|
||||||
|
scrollLastTick: Date.now()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
_onScrollTick(e) {
|
||||||
|
const { onScroll } = this.props
|
||||||
|
this.setState({
|
||||||
|
scrollLastTick: Date.now()
|
||||||
|
})
|
||||||
|
if (onScroll) onScroll(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
_onScrollEnd(e) {
|
||||||
|
const { onScroll } = this.props
|
||||||
|
this.setState({
|
||||||
|
isScrolling: false
|
||||||
|
})
|
||||||
|
if (onScroll) onScroll(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
_shouldEmitScrollEvent(lastTick, eventThrottle) {
|
||||||
|
const timeSinceLastTick = Date.now() - lastTick
|
||||||
|
return (eventThrottle > 0 && timeSinceLastTick >= (1000 / eventThrottle))
|
||||||
|
}
|
||||||
|
|
||||||
|
_maybePreventScroll(e) {
|
||||||
|
const { scrollEnabled } = this.props
|
||||||
|
if (!scrollEnabled) e.preventDefault()
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const {
|
||||||
|
children,
|
||||||
|
contentContainerStyle,
|
||||||
|
horizontal,
|
||||||
|
style
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
const resolvedStyle = pickProps(style, scrollViewStyleKeys)
|
||||||
|
const resolvedContentContainerStyle = pickProps(contentContainerStyle, scrollViewStyleKeys)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View {...this.props} />
|
<View
|
||||||
|
_className='ScrollView'
|
||||||
|
onScroll={(e) => this._onScroll(e)}
|
||||||
|
onTouchMove={(e) => this._maybePreventScroll(e)}
|
||||||
|
onWheel={(e) => this._maybePreventScroll(e)}
|
||||||
|
style={{
|
||||||
|
...styles.initial,
|
||||||
|
...resolvedStyle
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children ? (
|
||||||
|
<View
|
||||||
|
children={children}
|
||||||
|
style={{
|
||||||
|
...styles.initialContentContainer,
|
||||||
|
...resolvedContentContainerStyle,
|
||||||
|
...(horizontal && styles.row)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user