├── .gitignore ├── README.md ├── config ├── dev.js ├── index.js └── prod.js ├── package.json └── src ├── app.jsx ├── app.scss ├── index.html └── pages └── index ├── index.jsx └── index.scss /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Taro 高性能瀑布流组件(for RN) 2 | 3 | > 解决安卓低端机下拉下加载卡顿问题 -------------------------------------------------------------------------------- /config/dev.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | NODE_ENV: '"development"' 4 | }, 5 | defineConstants: { 6 | }, 7 | weapp: {}, 8 | h5: {} 9 | } 10 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | projectName: 'taro-waterfall', 3 | date: '2019-8-10', 4 | designWidth: 750, 5 | deviceRatio: { 6 | '640': 2.34 / 2, 7 | '750': 1, 8 | '828': 1.81 / 2 9 | }, 10 | sourceRoot: 'src', 11 | outputRoot: 'dist', 12 | plugins: { 13 | babel: { 14 | sourceMap: true, 15 | presets: [ 16 | ['env', { 17 | modules: false 18 | }] 19 | ], 20 | plugins: [ 21 | 'transform-decorators-legacy', 22 | 'transform-class-properties', 23 | 'transform-object-rest-spread' 24 | ] 25 | } 26 | }, 27 | defineConstants: { 28 | }, 29 | copy: { 30 | patterns: [ 31 | ], 32 | options: { 33 | } 34 | }, 35 | weapp: { 36 | module: { 37 | postcss: { 38 | autoprefixer: { 39 | enable: true, 40 | config: { 41 | browsers: [ 42 | 'last 3 versions', 43 | 'Android >= 4.1', 44 | 'ios >= 8' 45 | ] 46 | } 47 | }, 48 | pxtransform: { 49 | enable: true, 50 | config: { 51 | 52 | } 53 | }, 54 | url: { 55 | enable: true, 56 | config: { 57 | limit: 10240 // 设定转换尺寸上限 58 | } 59 | }, 60 | cssModules: { 61 | enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true 62 | config: { 63 | namingPattern: 'module', // 转换模式,取值为 global/module 64 | generateScopedName: '[name]__[local]___[hash:base64:5]' 65 | } 66 | } 67 | } 68 | } 69 | }, 70 | h5: { 71 | publicPath: '/', 72 | staticDirectory: 'static', 73 | module: { 74 | postcss: { 75 | autoprefixer: { 76 | enable: true, 77 | config: { 78 | browsers: [ 79 | 'last 3 versions', 80 | 'Android >= 4.1', 81 | 'ios >= 8' 82 | ] 83 | } 84 | }, 85 | cssModules: { 86 | enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true 87 | config: { 88 | namingPattern: 'module', // 转换模式,取值为 global/module 89 | generateScopedName: '[name]__[local]___[hash:base64:5]' 90 | } 91 | } 92 | } 93 | } 94 | } 95 | } 96 | 97 | module.exports = function (merge) { 98 | if (process.env.NODE_ENV === 'development') { 99 | return merge({}, config, require('./dev')) 100 | } 101 | return merge({}, config, require('./prod')) 102 | } 103 | -------------------------------------------------------------------------------- /config/prod.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | NODE_ENV: '"production"' 4 | }, 5 | defineConstants: { 6 | }, 7 | weapp: {}, 8 | h5: { 9 | /** 10 | * 如果h5端编译后体积过大,可以使用webpack-bundle-analyzer插件对打包体积进行分析。 11 | * 参考代码如下: 12 | * webpackChain (chain) { 13 | * chain.plugin('analyzer') 14 | * .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin, []) 15 | * } 16 | */ 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "taro-waterfall", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "Taro 高性能瀑布流组件(for RN)", 6 | "templateInfo": { 7 | "name": "default", 8 | "typescript": false, 9 | "css": "sass" 10 | }, 11 | "scripts": { 12 | "build:weapp": "taro build --type weapp", 13 | "build:swan": "taro build --type swan", 14 | "build:alipay": "taro build --type alipay", 15 | "build:tt": "taro build --type tt", 16 | "build:h5": "taro build --type h5", 17 | "build:rn": "taro build --type rn", 18 | "dev:weapp": "npm run build:weapp -- --watch", 19 | "dev:swan": "npm run build:swan -- --watch", 20 | "dev:alipay": "npm run build:alipay -- --watch", 21 | "dev:tt": "npm run build:tt -- --watch", 22 | "dev:h5": "npm run build:h5 -- --watch", 23 | "dev:rn": "npm run build:rn -- --watch" 24 | }, 25 | "author": "", 26 | "license": "MIT", 27 | "dependencies": { 28 | "@tarojs/components": "1.3.9", 29 | "@tarojs/router": "1.3.9", 30 | "@tarojs/taro": "1.3.9", 31 | "@tarojs/taro-alipay": "1.3.9", 32 | "@tarojs/taro-h5": "1.3.9", 33 | "@tarojs/taro-swan": "1.3.9", 34 | "@tarojs/taro-tt": "1.3.9", 35 | "@tarojs/taro-weapp": "1.3.9", 36 | "nervjs": "^1.4.0", 37 | "nerv-devtools": "^1.4.0" 38 | }, 39 | "devDependencies": { 40 | "@types/react": "^16.4.6", 41 | "@types/webpack-env": "^1.13.6", 42 | "@tarojs/plugin-babel": "1.3.9", 43 | "@tarojs/plugin-csso": "1.3.9", 44 | "@tarojs/plugin-sass": "1.3.9", 45 | "@tarojs/plugin-uglifyjs": "1.3.9", 46 | "@tarojs/webpack-runner": "1.3.9", 47 | "babel-plugin-transform-class-properties": "^6.24.1", 48 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 49 | "babel-plugin-transform-jsx-stylesheet": "^0.6.5", 50 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 51 | "babel-preset-env": "^1.6.1", 52 | "babel-eslint": "^8.2.3", 53 | "eslint": "^5.16.0", 54 | "eslint-config-taro": "1.3.9", 55 | "eslint-plugin-react": "^7.8.2", 56 | "eslint-plugin-import": "^2.12.0", 57 | "stylelint": "9.3.0", 58 | "stylelint-config-taro-rn": "1.3.9", 59 | "stylelint-taro-rn": "1.3.9", 60 | "eslint-plugin-taro": "1.3.9" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/app.jsx: -------------------------------------------------------------------------------- 1 | import Taro, { Component } from '@tarojs/taro' 2 | import Index from './pages/index' 3 | 4 | import './app.scss' 5 | 6 | // 如果需要在 h5 环境中开启 React Devtools 7 | // 取消以下注释: 8 | // if (process.env.NODE_ENV !== 'production' && process.env.TARO_ENV === 'h5') { 9 | // require('nerv-devtools') 10 | // } 11 | 12 | class App extends Component { 13 | 14 | config = { 15 | pages: [ 16 | 'pages/index/index' 17 | ], 18 | window: { 19 | backgroundTextStyle: 'light', 20 | navigationBarBackgroundColor: '#fff', 21 | navigationBarTitleText: 'WeChat', 22 | navigationBarTextStyle: 'black' 23 | } 24 | } 25 | 26 | componentDidMount () {} 27 | 28 | componentDidShow () {} 29 | 30 | componentDidHide () {} 31 | 32 | componentDidCatchError () {} 33 | 34 | // 在 App 类中的 render() 函数没有实际作用 35 | // 请勿修改此函数 36 | render () { 37 | return ( 38 | 39 | ) 40 | } 41 | } 42 | 43 | Taro.render(, document.getElementById('app')) 44 | -------------------------------------------------------------------------------- /src/app.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aNd1coder/taro-waterfall/2fdd1557cf1dd1a3f1fc5df0e2271c2209e48178/src/app.scss -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /src/pages/index/index.jsx: -------------------------------------------------------------------------------- 1 | import Taro, { Component } from '@tarojs/taro' 2 | import { View, Text, Image } from '@tarojs/components' 3 | import './index.scss' 4 | 5 | Taro.initPxTransform({ designWidth: 750 }) 6 | 7 | // 一般app 只有竖屏模式,所以可以只获取一次 width 8 | const deviceWidthDp = Dimensions.get('window').width 9 | const uiWidthPx = 375 10 | 11 | let wrapperOffsetY = 0 12 | let index = 0 13 | 14 | export default class Index extends Component { 15 | config = { 16 | navigationBarTitleText: '首页' 17 | } 18 | 19 | state = { 20 | list: [ 21 | { 22 | cover: 'https://jdc.jd.com/img/375', 23 | title: '测试' 24 | } 25 | ] 26 | } 27 | 28 | _onLayout(event) { 29 | const { layout } = event.nativeEvent 30 | const { y } = layout 31 | wrapperOffsetY = y 32 | } 33 | 34 | scalePx2dp(uiElementPx) { 35 | return uiElementPx * deviceWidthDp / uiWidthPx 36 | } 37 | 38 | pxTransform(value) { 39 | return process.env.TARO_ENV === 'rn' ? Taro.pxTransform(this.scalePx2dp(value)) : Taro.pxTransform(value) 40 | } 41 | 42 | // 获取视窗高度 43 | getMainHeight () { 44 | const info = Taro.getSystemInfoSync() 45 | const { windowHeight } = info 46 | 47 | return windowHeight 48 | } 49 | 50 | loadData () { 51 | index++ 52 | const list = [{ 53 | cover: 'https://jdc.jd.com/img/375', 54 | title: `测试${index}` 55 | }].concat(this.state.list) 56 | 57 | this.setState({ list }) 58 | } 59 | 60 | // 下拉加载数据 61 | handleReachBottom() { 62 | this.loadData() 63 | } 64 | 65 | // 检查元素是否在视窗内 66 | checkItemIsInView(item, itemOffsetY) { 67 | const info = Taro.getSystemInfoSync() 68 | const { windowHeight } = info 69 | const threshold = windowHeight * 3 70 | const { scrollTop } = this.props 71 | const titleHeight = this.pxTransform(40) // 可以根据实际场景动态计算 72 | const offsetY = wrapperOffsetY + itemOffsetY + titleHeight 73 | const itemBottom = offsetY + item.realHeight 74 | const bottom = windowHeight + scrollTop 75 | 76 | if ((offsetY - threshold) > bottom || (itemBottom + threshold) < scrollTop) { 77 | return false 78 | } 79 | 80 | return true 81 | } 82 | 83 | render() { 84 | const data = this.props.data 85 | 86 | if (!data) return null 87 | 88 | const listLeft = [] 89 | const listRight = [] 90 | const gap = 20 91 | 92 | let leftTotalHeigt = 0 93 | let rightTotalHeigt = 0 94 | 95 | data.forEach((item, index) => { 96 | const itemHeight = 500 // 根据实际场景动态计算 97 | const realHeight = pxTransform(itemHeight) 98 | const addedHeight = itemHeight + gap 99 | item.index = index + 1 100 | item.height = itemHeight 101 | item.realHeight = realHeight 102 | 103 | if (leftTotalHeigt <= rightTotalHeigt) { 104 | item.isInView = this.checkItemIsInView(item, this.pxTransform(leftTotalHeigt)) 105 | listLeft.push(item) 106 | leftTotalHeigt += addedHeight 107 | } else { 108 | item.isInView = this.checkItemIsInView(item, this.pxTransform(rightTotalHeigt)) 109 | listRight.push(item) 110 | rightTotalHeigt += addedHeight 111 | } 112 | }) 113 | 114 | const style = { 115 | height: data.realHeight 116 | } 117 | 118 | const renderWaterfallBlock = (data) => { 119 | 120 | {data.isInView && ( 121 | 122 | 123 | 124 | 125 | {data.title} 126 | 127 | 128 | 129 | )} 130 | 131 | } 132 | 133 | return ( 134 | 144 | 145 | 146 | { 147 | listLeft.map(item => { 148 | return renderWaterfallBlock(item) 149 | }) 150 | } 151 | 152 | 153 | { 154 | listRight.map(item => { 155 | return renderWaterfallBlock(item) 156 | }) 157 | } 158 | 159 | 160 | 161 | ) 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/pages/index/index.scss: -------------------------------------------------------------------------------- 1 | .waterfall { 2 | width: 750px; 3 | display: flex; 4 | flex-direction: row; 5 | justify-content: space-between; 6 | padding: 20px 30px; 7 | 8 | &-col { 9 | width: 335px; 10 | } 11 | 12 | &-block { 13 | width: 335px; 14 | margin-bottom: 20px; 15 | background-color: #ffffff; 16 | border-radius: 10px; 17 | overflow: hidden; 18 | 19 | &__cover { 20 | width: 335px; 21 | height: 335px; 22 | margin-bottom: 10px; 23 | } 24 | 25 | &__title { 26 | height: 84px; 27 | min-height: 100%; 28 | } 29 | } 30 | } --------------------------------------------------------------------------------