Repository: react-ld/react-pullLoad
Branch: master
Commit: 62ba824bac1b
Files: 32
Total size: 78.4 KB
Directory structure:
gitextract_gef5ggiu/
├── .babelrc
├── .gitignore
├── README-cn.md
├── README.md
├── example/
│ ├── App.css
│ ├── App1.jsx
│ ├── App2.jsx
│ ├── App3.jsx
│ ├── App4.jsx
│ ├── README.md
│ ├── index1.html
│ ├── index2.html
│ ├── index3.html
│ └── index4.html
├── gulpfile.js
├── lib/
│ ├── FooterNode.js
│ ├── HeadNode.js
│ ├── ReactPullLoad.js
│ ├── ReactPullLoad.less
│ ├── constants.js
│ └── index.js
├── package.json
├── postcss.config.js
├── src/
│ ├── FooterNode.jsx
│ ├── HeadNode.jsx
│ ├── ReactPullLoad.jsx
│ ├── ReactPullLoad.less
│ ├── constants.js
│ ├── index.d.ts
│ └── index.js
├── webpack.config.example.js
└── webpack.config.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .babelrc
================================================
{
"presets": ["es2015", "stage-1", "react"]
}
================================================
FILE: .gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
package-lock.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Typescript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
demo
deploy.config.json
dist
.publish
================================================
FILE: README-cn.md
================================================
# [English](./README.md)
# [react-pullLoad](https://github.com/react-ld/react-pullLoad)
React 版本的 [pullLoad](https://github.com/lidianhao123/pullLoad) 下拉更新 上拉加载更多 组件
[pullLoad](https://github.com/lidianhao123/pullLoad) 非 react 版本,支持 require.js 模块化调用
#### 示例
[demo1](https://react-ld.github.io/react-pullLoad/index1.html) ReactPullLoad 根节点 DOM 作为容器
[demo2](https://react-ld.github.io/react-pullLoad/index2.html) ReactPullLoad 根节点 DOM 作为容器
[demo3](https://react-ld.github.io/react-pullLoad/index3.html) document.body 作为容器 且自定义刷新和加载更多 UI 组件
[demo4](https://react-ld.github.io/react-pullLoad/index4.html) 禁用下拉刷新功能
# 当前版本 1.2.0
支持 Typescript
# 简介
1. 只依赖 react/react-dom
2. 样式使用 less 编写
3. 支持 body 或者组件根 DOM 固定高度作为外部容器 contianer(即可视区域大小)
4. 触摸事件绑定在内容块 content(即高度为 auto 的 DOM )
5. 纯 React 组件方式开发的
6. 支持自定义刷新和加载更多 UI 组件
7. 支持代码动态调起刷新和加载更多(组件将展示刷新和加载更多样式)
8. **只支持移动触摸设备**
# 功能点
1. 下拉距离大于阈值触发刷新动作
2. 滚动到距底部距离小于阈值加载更多
3. 支持自定义刷新和加载更多 UI 组件
# 使用说明
```sh
npm install --save react-pullload
```
```js
import ReactPullLoad, { STATS } from "react-pullload";
import "node_modules/react-pullload/dist/ReactPullLoad.css";
export class App extends Component {
constructor() {
super();
this.state = {
hasMore: true,
data: cData,
action: STATS.init,
index: loadMoreLimitNum //loading more test time limit
};
}
handleAction = action => {
console.info(action, this.state.action, action === this.state.action);
//new action must do not equel to old action
if (action === this.state.action) {
return false;
}
if (action === STATS.refreshing) {
//刷新
this.handRefreshing();
} else if (action === STATS.loading) {
//加载更多
this.handLoadMore();
} else {
//DO NOT modify below code
this.setState({
action: action
});
}
};
handRefreshing = () => {
if (STATS.refreshing === this.state.action) {
return false;
}
setTimeout(() => {
//refreshing complete
this.setState({
data: cData,
hasMore: true,
action: STATS.refreshed,
index: loadMoreLimitNum
});
}, 3000);
this.setState({
action: STATS.refreshing
});
};
handLoadMore = () => {
if (STATS.loading === this.state.action) {
return false;
}
//无更多内容则不执行后面逻辑
if (!this.state.hasMore) {
return;
}
setTimeout(() => {
if (this.state.index === 0) {
this.setState({
action: STATS.reset,
hasMore: false
});
} else {
this.setState({
data: [...this.state.data, cData[0], cData[0]],
action: STATS.reset,
index: this.state.index - 1
});
}
}, 3000);
this.setState({
action: STATS.loading
});
};
render() {
const { data, hasMore } = this.state;
const fixHeaderStyle = {
position: "fixed",
width: "100%",
height: "50px",
color: "#fff",
lineHeight: "50px",
backgroundColor: "#e24f37",
left: 0,
top: 0,
textAlign: "center",
zIndex: 1
};
return (
<div>
<div style={fixHeaderStyle}>fixed header</div>
<ReactPullLoad
downEnough={150}
action={this.state.action}
handleAction={this.handleAction}
hasMore={hasMore}
style={{ paddingTop: 50 }}
distanceBottom={1000}
>
<ul className="test-ul">
<button onClick={this.handRefreshing}>refreshing</button>
<button onClick={this.handLoadMore}>loading more</button>
{data.map((str, index) => {
return (
<li key={index}>
<img src={str} alt="" />
</li>
);
})}
</ul>
</ReactPullLoad>
</div>
);
}
}
```
# 参数说明:
| 参数 | 说明 | 类型 | 默认值 | 备注 |
| ---------------- | --------------------------------------------- | ------ | ------------------------------------------------ | --------------------- |
| action | 用于同步状态 | string | | isRequired |
| handleAction | 用于处理状态 | func | | isRequired |
| hasMore | 是否还有更多内容可加载 | bool | false | |
| downEnough | 下拉距离是否满足要求 | num | 100 | |
| distanceBottom | 距离底部距离触发加载更多 | num | 100 | |
| isBlockContainer | 是否开启使用组件根 DOM 作为外部容器 contianer | bool | false | |
| HeadNode | 自定义顶部刷新 UI 组件 | any | [ReactPullLoad HeadNode](./src/HeadNode.jsx) | 必须是一个 React 组件 |
| FooterNode | 自定义底部加载更多 UI 组件 | any | [ReactPullLoad FooterNode](./src/FooterNode.jsx) | 必须是一个 React 组件 |
另外 ReactPullLoad 组件支持根属性扩展 例如: className\style 等等
# STATS list
| 属性 | 值 | 根节点 className | 说明 |
| ---------- | ---------------- | -------------------- | -------------------- |
| init | '' | | 组件初始状态 |
| pulling | 'pulling' | state-pulling | 下拉状态 |
| enough | 'pulling enough' | state-pulling.enough | 下拉并且已经满足阈值 |
| refreshing | 'refreshing' | state-refreshing | 刷新中(加载数据中) |
| refreshed | 'refreshed' | state-refreshed | 完成刷新动作 |
| reset | 'reset' | state-reset | 恢复默认状态 |
| loading | 'loading' | state-loading | 加载中 |
init/reset -> pulling -> enough -> refreshing -> refreshed -> reset
init/reset -> pulling -> reset
init/reset -> loading -> reset
# 自定义刷新及加载组件
请参考默认刷新及加载组件源码(通过 css 根节点不同 className 设置对应 UI 样式来实现)
[ReactPullLoad HeadNode](./src/HeadNode.jsx)
[ReactPullLoad FooterNode](./src/FooterNode.jsx)
或参考 demo3 中的实现方式在组件内容通过获取的 loaderState 与 STATS 不同状态对比来现实
[demo3](https://react-ld.github.io/react-pullLoad/index3.html)
# License
MIT
================================================
FILE: README.md
================================================
# [中文](./README-cn.md)
# [react-pullLoad](https://github.com/react-ld/react-pullLoad)
Refreshing and Loading more component for react.
[pullLoad](https://github.com/lidianhao123/pullLoad) is another refreshing and loading more lib without react, support require.js to load lib.
#### examples
[demo1](https://react-ld.github.io/react-pullLoad/index1.html) use ReactPullLoad root DOM as container
[demo2](https://react-ld.github.io/react-pullLoad/index2.html) use ReactPullLoad root DOM as container
[demo3](https://react-ld.github.io/react-pullLoad/index3.html) use document.body as container, and config UI component (HeadNode and FooterNode).
[demo4](https://react-ld.github.io/react-pullLoad/index4.html) forbidden pull refresh
# version 1.2.0
Support Typescript
# Description
1. Only depend on react/react-dom, without any other package.
2. Use less.
3. Support body or root Dom as container.
4. Bind touch event on component root Dom.
5. It.s develop as Pure react component.
6. Support config UI component (HeadNode and FooterNode).
7. Can apply refreshing or loading through modify STATE.
8. **Only support mobile device**
# How to use
```sh
npm install --save react-pullload
```
```js
import ReactPullLoad, { STATS } from "react-pullload";
import "node_modules/react-pullload/dist/ReactPullLoad.css";
export class App extends Component {
constructor() {
super();
this.state = {
hasMore: true,
data: cData,
action: STATS.init,
index: loadMoreLimitNum //loading more test time limit
};
}
handleAction = action => {
console.info(action, this.state.action, action === this.state.action);
//new action must do not equel to old action
if (action === this.state.action) {
return false;
}
if (action === STATS.refreshing) {
this.handRefreshing();
} else if (action === STATS.loading) {
this.handLoadMore();
} else {
//DO NOT modify below code
this.setState({
action: action
});
}
};
handRefreshing = () => {
if (STATS.refreshing === this.state.action) {
return false;
}
setTimeout(() => {
//refreshing complete
this.setState({
data: cData,
hasMore: true,
action: STATS.refreshed,
index: loadMoreLimitNum
});
}, 3000);
this.setState({
action: STATS.refreshing
});
};
handLoadMore = () => {
if (STATS.loading === this.state.action) {
return false;
}
//无更多内容则不执行后面逻辑
if (!this.state.hasMore) {
return;
}
setTimeout(() => {
if (this.state.index === 0) {
this.setState({
action: STATS.reset,
hasMore: false
});
} else {
this.setState({
data: [...this.state.data, cData[0], cData[0]],
action: STATS.reset,
index: this.state.index - 1
});
}
}, 3000);
this.setState({
action: STATS.loading
});
};
render() {
const { data, hasMore } = this.state;
const fixHeaderStyle = {
position: "fixed",
width: "100%",
height: "50px",
color: "#fff",
lineHeight: "50px",
backgroundColor: "#e24f37",
left: 0,
top: 0,
textAlign: "center",
zIndex: 1
};
return (
<div>
<div style={fixHeaderStyle}>fixed header</div>
<ReactPullLoad
downEnough={150}
action={this.state.action}
handleAction={this.handleAction}
hasMore={hasMore}
style={{ paddingTop: 50 }}
distanceBottom={1000}
>
<ul className="test-ul">
<button onClick={this.handRefreshing}>refreshing</button>
<button onClick={this.handLoadMore}>loading more</button>
{data.map((str, index) => {
return (
<li key={index}>
<img src={str} alt="" />
</li>
);
})}
</ul>
</ReactPullLoad>
</div>
);
}
}
```
# API:
| Property | Description | Type | default | Remarks |
| ---------------- | ------------------------------------------- | ------ | ------------------------------------------------ | ------------------------- |
| action | sync component status | string | | isRequired |
| handleAction | handle status | func | | isRequired |
| hasMore | flag for are there any more content to load | bool | false | |
| downEnough | how long distance is enough to refreshing | num | 100 | use px as unit |
| distanceBottom | current position is apart from bottom | num | 100 | use px as unit |
| isBlockContainer | set root dom as container | bool | false | |
| HeadNode | custom header UI compoent | any | [ReactPullLoad HeadNode](./src/HeadNode.jsx) | must be a react component |
| FooterNode | custom footer UI compoent | any | [ReactPullLoad FooterNode](./src/FooterNode.jsx) | must be a react component |
Remarks: ReactPullLoad support set root dom className and style.
# STATS list
| Property | Value | root className | explain |
| ---------- | ---------------- | -------------------- | ---------------------------- |
| init | '' | | component initial status |
| pulling | 'pulling' | state-pulling | pull status |
| enough | 'pulling enough' | state-pulling.enough | pull down enough status |
| refreshing | 'refreshing' | state-refreshing | refreshing status fetch data |
| refreshed | 'refreshed' | state-refreshed | refreshed |
| reset | 'reset' | state-reset | reset status |
| loading | 'loading' | state-loading | fetching data |
init/reset -> pulling -> enough -> refreshing -> refreshed -> reset
init/reset -> pulling -> reset
init/reset -> loading -> reset
# Custom UI components
Please refer to the default HeadNode and FooterNode components
[ReactPullLoad HeadNode](./src/HeadNode.jsx)
[ReactPullLoad FooterNode](./src/FooterNode.jsx)
Or refer to demo3, show different dom style through compare props loaderState width STATS.
[demo3](https://react-ld.github.io/react-pullLoad/index3.html)
# License
MIT
================================================
FILE: example/App.css
================================================
html,body{margin: 0; padding: 0;}
li{font-size: 20px; width: 100%;list-style: none;}
img{width: 100%;}
div, .test-ul, p{margin: 0; padding: 0;}
.block{position: absolute; top:0; left:0; box-sizing: border-box; height: 100%;box-sizing: border-box;}
button{
display: inline-block;
font-weight: 500;
text-align: center;
-ms-touch-action: manipulation;
touch-action: manipulation;
cursor: pointer;
background-image: none;
border: 1px solid transparent;
white-space: nowrap;
line-height: 1.5;
padding: 4px 15px;
font-size: 12px;
border-radius: 4px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-transition: all .3s cubic-bezier(.645,.045,.355,1);
transition: all .3s cubic-bezier(.645,.045,.355,1);
position: relative;
color: rgba(0,0,0,.65);
background-color: #fff;
border-color: #d9d9d9;
outline: 0;
margin-right: 8px;
margin-bottom: 12px;
margin-top: 12px;
-webkit-appearance: button;
box-sizing: border-box;
}
================================================
FILE: example/App1.jsx
================================================
import React, { Component, PureComponent } from 'react'
import PropTypes from 'prop-types';
import { render } from 'react-dom'
import ReactPullLoad,{STATS} from 'index.js'
import '../src/ReactPullLoad.less'
import './App.css'
const defaultStyle ={
width: "100%",
textAlign: "center",
fontSize: "20px",
lineHeight: "1.5"
}
const loadMoreLimitNum = 2;
const cData = [
"http://img1.gtimg.com/15/1580/158031/15803178_1200x1000_0.jpg",
"http://img1.gtimg.com/15/1580/158031/15803179_1200x1000_0.jpg",
"http://img1.gtimg.com/15/1580/158031/15803181_1200x1000_0.jpg",
"http://img1.gtimg.com/15/1580/158031/15803182_1200x1000_0.jpg",
"http://img1.gtimg.com/15/1580/158031/15803183_1200x1000_0.jpg",
// "http://img1.gtimg.com/15/1580/158031/15803184_1200x1000_0.jpg",
// "http://img1.gtimg.com/15/1580/158031/15803186_1200x1000_0.jpg"
]
export class App extends Component{
constructor(){
super();
this.state ={
hasMore: true,
data: cData,
action: STATS.init,
index: loadMoreLimitNum //loading more test time limit
}
}
handleAction = (action) => {
console.info(action, this.state.action,action === this.state.action);
//new action must do not equel to old action
if(action === this.state.action ||
action === STATS.refreshing && this.state.action === STATS.loading ||
action === STATS.loading && this.state.action === STATS.refreshing){
// console.info("It's same action or on loading or on refreshing ",action, this.state.action,action === this.state.action);
return false
}
if(action === STATS.refreshing){//刷新
setTimeout(()=>{
//refreshing complete
this.setState({
data: cData,
hasMore: true,
action: STATS.refreshed,
index: loadMoreLimitNum
});
}, 3000)
} else if(action === STATS.loading){//加载更多
this.setState({
hasMore: true
});
setTimeout(()=>{
if(this.state.index === 0){
this.setState({
action: STATS.reset,
hasMore: false
});
} else{
this.setState({
data: [...this.state.data, cData[0], cData[0]],
action: STATS.reset,
index: this.state.index - 1
});
}
}, 3000)
}
//DO NOT modify below code
this.setState({
action: action
})
}
getScrollTop = ()=>{
if(this.refs.reactpullload){
console.info(this.refs.reactpullload.getScrollTop());
}
}
setScrollTop = ()=>{
if(this.refs.reactpullload){
console.info(this.refs.reactpullload.setScrollTop(100));
}
}
render(){
const {
data,
hasMore
} = this.state
const fixHeaderStyle = {
position: "fixed",
width: "100%",
height: "50px",
color: "#fff",
lineHeight: "50px",
backgroundColor: "#e24f37",
left: 0,
top: 0,
textAlign: "center",
zIndex: 1
}
const fixButtonStyle = {
position: "fixed",
top: 200,
width: "100%",
}
return (
<div>
<div style={fixHeaderStyle}>
fixed header
</div>
<ReactPullLoad
downEnough={150}
ref="reactpullload"
className="block"
isBlockContainer={true}
action={this.state.action}
handleAction={this.handleAction}
hasMore={hasMore}
style={{paddingTop: 50}}
distanceBottom={1000}>
<ul className="test-ul">
<button onClick={this.handleAction.bind(this, STATS.refreshing)}>refreshing</button>
<button onClick={this.handleAction.bind(this, STATS.loading)}>loading more</button>
<div style={fixButtonStyle}>
<button onClick={this.getScrollTop}>getScrollTop</button>
<button onClick={this.setScrollTop}>setScrollTop</button>
</div>
{
data.map( (str, index )=>{
return <li key={index}><img src={str} alt=""/></li>
})
}
</ul>
</ReactPullLoad>
</div>
)
}
}
render(
<App />,
document.getElementById('root')
)
================================================
FILE: example/App2.jsx
================================================
import React, { Component, PureComponent } from 'react'
import PropTypes from 'prop-types';
import { render } from 'react-dom'
import ReactPullLoad,{STATS} from 'index.js'
import '../src/ReactPullLoad.less'
import './App.css'
const defaultStyle ={
width: "100%",
textAlign: "center",
fontSize: "20px",
lineHeight: "1.5"
}
const loadMoreLimitNum = 2;
const cData = [
"http://img1.gtimg.com/15/1580/158031/15803178_1200x1000_0.jpg",
"http://img1.gtimg.com/15/1580/158031/15803179_1200x1000_0.jpg",
"http://img1.gtimg.com/15/1580/158031/15803181_1200x1000_0.jpg",
"http://img1.gtimg.com/15/1580/158031/15803182_1200x1000_0.jpg",
"http://img1.gtimg.com/15/1580/158031/15803183_1200x1000_0.jpg",
// "http://img1.gtimg.com/15/1580/158031/15803184_1200x1000_0.jpg",
// "http://img1.gtimg.com/15/1580/158031/15803186_1200x1000_0.jpg"
]
export class App extends Component{
constructor(){
super();
this.state ={
hasMore: true,
data: cData,
action: STATS.init,
index: loadMoreLimitNum //loading more test time limit
}
}
handleAction = (action) => {
console.info(action, this.state.action,action === this.state.action);
//new action must do not equel to old action
if(action === this.state.action ||
action === STATS.refreshing && this.state.action === STATS.loading ||
action === STATS.loading && this.state.action === STATS.refreshing){
console.info("It's same action or on loading or on refreshing ",action, this.state.action,action === this.state.action);
return false
}
if(action === STATS.refreshing){//刷新
setTimeout(()=>{
//refreshing complete
this.setState({
data: cData,
hasMore: true,
action: STATS.refreshed,
index: loadMoreLimitNum
});
}, 3000)
} else if(action === STATS.loading){//加载更多
this.setState({
hasMore: true
});
setTimeout(()=>{
if(this.state.index === 0){
this.setState({
action: STATS.reset,
hasMore: false
});
} else{
this.setState({
data: [...this.state.data, cData[0], cData[0]],
action: STATS.reset,
index: this.state.index - 1
});
}
}, 3000)
}
//DO NOT modify below code
this.setState({
action: action
})
}
render(){
const {
data,
hasMore
} = this.state
return (
<div>
<ReactPullLoad
className="block"
isBlockContainer={true}
downEnough={150}
action={this.state.action}
handleAction={this.handleAction}
hasMore={hasMore}
distanceBottom={1000}>
<ul className="test-ul">
<button onClick={this.handleAction.bind(this, STATS.refreshing)}>refreshing</button>
<button onClick={this.handleAction.bind(this, STATS.loading)}>loading more</button>
{
data.map( (str, index )=>{
return <li key={index}><img src={str} alt=""/></li>
})
}
</ul>
</ReactPullLoad>
</div>
)
}
}
render(
<App />,
document.getElementById('root')
)
================================================
FILE: example/App3.jsx
================================================
import React, { Component, PureComponent } from 'react'
import PropTypes from 'prop-types';
import { render } from 'react-dom'
import ReactPullLoad,{STATS} from 'index.js'
import '../src/ReactPullLoad.less'
import './App.css'
const defaultStyle ={
width: "100%",
textAlign: "center",
fontSize: "20px",
lineHeight: "1.5"
}
class HeadNode extends PureComponent{
static propTypes = {
loaderState: PropTypes.string.isRequired,
};
static defaultProps = {
loaderState: STATS.init,
};
render(){
const {
loaderState
} = this.props
let content = ""
if(loaderState == STATS.pulling){
content = "下拉刷新"
} else if(loaderState == STATS.enough){
content = "松开刷新"
} else if(loaderState == STATS.refreshing){
content = "正在刷新..."
} else if(loaderState == STATS.refreshed){
content = "刷新成功"
}
return(
<div style={defaultStyle}>
{content}
</div>
)
}
}
class FooterNode extends PureComponent{
static propTypes = {
loaderState: PropTypes.string.isRequired,
hasMore: PropTypes.bool.isRequired
};
static defaultProps = {
loaderState: STATS.init,
hasMore: true
};
render(){
const {
loaderState,
hasMore
} = this.props
let content = ""
// if(hasMore === false){
// content = "没有更多"
// } else if(loaderState == STATS.loading && hasMore === true){
// content = "加载中"
// }
if(loaderState == STATS.loading){
content = "加载中"
} else if(hasMore === false){
content = "没有更多"
}
return(
<div style={defaultStyle}>
{content}
</div>
)
}
}
const loadMoreLimitNum = 2;
const cData = [
"http://img1.gtimg.com/15/1580/158031/15803178_1200x1000_0.jpg",
"http://img1.gtimg.com/15/1580/158031/15803179_1200x1000_0.jpg",
"http://img1.gtimg.com/15/1580/158031/15803181_1200x1000_0.jpg",
"http://img1.gtimg.com/15/1580/158031/15803182_1200x1000_0.jpg",
"http://img1.gtimg.com/15/1580/158031/15803183_1200x1000_0.jpg",
// "http://img1.gtimg.com/15/1580/158031/15803184_1200x1000_0.jpg",
// "http://img1.gtimg.com/15/1580/158031/15803186_1200x1000_0.jpg"
]
export class App extends Component{
constructor(){
super();
this.state ={
hasMore: true,
data: cData,
action: STATS.init,
index: loadMoreLimitNum //loading more test time limit
}
}
handleAction = (action) => {
//new action must do not equel to old action
if(action === this.state.action ||
action === STATS.refreshing && this.state.action === STATS.loading ||
action === STATS.loading && this.state.action === STATS.refreshing){
console.info("It's same action or on loading or on refreshing ",action, this.state.action,action === this.state.action);
return false
}
if(action === STATS.refreshing){//刷新
setTimeout(()=>{
//refreshing complete
this.setState({
data: cData,
hasMore: true,
action: STATS.refreshed,
index: loadMoreLimitNum
});
}, 3000)
} else if(action === STATS.loading && this.state.hasMore){//加载更多
setTimeout(()=>{
if(this.state.index === 0){
this.setState({
action: STATS.reset,
hasMore: false
});
} else{
this.setState({
data: [...this.state.data, cData[0], cData[0]],
action: STATS.reset,
index: this.state.index - 1
});
}
}, 3000)
}
//无更多内容,不再加载数据
if(action === STATS.loading && !this.state.hasMore){
return;
}
//DO NOT modify below code
this.setState({
action: action
})
}
render(){
const {
data,
hasMore
} = this.state
const fixHeaderStyle = {
position: "fixed",
width: "100%",
height: "50px",
color: "#fff",
lineHeight: "50px",
backgroundColor: "#e24f37",
left: 0,
top: 0,
textAlign: "center",
zIndex: 1
}
return (
<div>
<div style={fixHeaderStyle}>
fixed header
</div>
<ReactPullLoad
downEnough={100}
action={this.state.action}
handleAction={this.handleAction}
hasMore={hasMore}
HeadNode={HeadNode}
FooterNode={FooterNode}
style={{paddingTop: 50}}
distanceBottom={1000}>
<ul className="test-ul">
<button onClick={this.handleAction.bind(this, STATS.refreshing)}>refreshing</button>
<button onClick={this.handleAction.bind(this, STATS.loading)}>loading more</button>
{
data.map( (str, index )=>{
return <li key={index}><img src={str} alt=""/></li>
})
}
</ul>
</ReactPullLoad>
</div>
)
}
}
render(
<App />,
document.getElementById('root')
)
================================================
FILE: example/App4.jsx
================================================
import React, { Component, PureComponent } from 'react'
import PropTypes from 'prop-types';
import { render } from 'react-dom'
import ReactPullLoad,{STATS} from 'index.js'
import '../src/ReactPullLoad.less'
import './App.css'
const defaultStyle ={
width: "100%",
textAlign: "center",
fontSize: "20px",
lineHeight: "1.5"
}
const loadMoreLimitNum = 2;
const cData = [
"http://img1.gtimg.com/15/1580/158031/15803178_1200x1000_0.jpg",
"http://img1.gtimg.com/15/1580/158031/15803179_1200x1000_0.jpg",
"http://img1.gtimg.com/15/1580/158031/15803181_1200x1000_0.jpg",
"http://img1.gtimg.com/15/1580/158031/15803182_1200x1000_0.jpg",
"http://img1.gtimg.com/15/1580/158031/15803183_1200x1000_0.jpg",
// "http://img1.gtimg.com/15/1580/158031/15803184_1200x1000_0.jpg",
// "http://img1.gtimg.com/15/1580/158031/15803186_1200x1000_0.jpg"
]
export class App extends Component{
constructor(){
super();
this.state ={
hasMore: true,
data: cData,
action: STATS.init,
index: loadMoreLimitNum //loading more test time limit
}
}
handleAction = (action) => {
console.info(action, this.state.action,action === this.state.action);
if(action !== STATS.loading){
return false;
}
this.setState({
hasMore: true
});
setTimeout(()=>{
if(this.state.index === 0){
this.setState({
action: STATS.reset,
hasMore: false
});
} else{
this.setState({
data: [...this.state.data, cData[0], cData[0]],
action: STATS.reset,
index: this.state.index - 1
});
}
}, 3000)
//DO NOT modify below code
this.setState({
action: action
})
}
getScrollTop = ()=>{
if(this.refs.reactpullload){
console.info(this.refs.reactpullload.getScrollTop());
}
}
setScrollTop = ()=>{
if(this.refs.reactpullload){
console.info(this.refs.reactpullload.setScrollTop(100));
}
}
render(){
const {
data,
hasMore
} = this.state
const fixHeaderStyle = {
position: "fixed",
width: "100%",
height: "50px",
color: "#fff",
lineHeight: "50px",
backgroundColor: "#e24f37",
left: 0,
top: 0,
textAlign: "center",
zIndex: 1
}
const fixButtonStyle = {
position: "fixed",
top: 200,
width: "100%",
}
return (
<div>
<div style={fixHeaderStyle}>
fixed header
</div>
<ReactPullLoad
downEnough={150}
ref="reactpullload"
className="block"
isBlockContainer={true}
action={this.state.action}
handleAction={this.handleAction}
hasMore={hasMore}
style={{paddingTop: 50}}
distanceBottom={1000}>
<ul className="test-ul">
<button onClick={this.handleAction.bind(this, STATS.refreshing)}>refreshing</button>
<button onClick={this.handleAction.bind(this, STATS.loading)}>loading more</button>
<div style={fixButtonStyle}>
<button onClick={this.getScrollTop}>getScrollTop</button>
<button onClick={this.setScrollTop}>setScrollTop</button>
</div>
{
data.map( (str, index )=>{
return <li key={index}><img src={str} alt=""/></li>
})
}
</ul>
</ReactPullLoad>
</div>
)
}
}
render(
<App />,
document.getElementById('root')
)
================================================
FILE: example/README.md
================================================
# 示例
[demo1](https://react-ld.github.io/react-pullLoad/index1.html) ReactPullLoad 根节点 DOM 作为容器
[demo2](https://react-ld.github.io/react-pullLoad/index2.html) ReactPullLoad 根节点 DOM 作为容器
[demo3](https://react-ld.github.io/react-pullLoad/index3.html) document.body 作为容器 且自定义刷新和加载更多 UI 组件
[demo4](https://react-ld.github.io/react-pullLoad/index4.html) 禁用下拉刷新功能
# 文档
[react-pullLoad](https://github.com/react-ld/react-pullLoad)
================================================
FILE: example/index1.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ReactPullLoad demo1</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
</head>
<body>
<div id="root"></div>
<!--
bundle1.js is demo use document as contianer
bundle2.js is demo use ReactPullLoad root DOM as contianer
-->
<script src="./bundle1.js"></script>
</body>
</html>
================================================
FILE: example/index2.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ReactPullLoad demo2</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
</head>
<body>
<div id="root"></div>
<!--
bundle1.js is demo use document as contianer
bundle2.js is demo use ReactPullLoad root DOM as contianer
-->
<script src="./bundle2.js"></script>
</body>
</html>
================================================
FILE: example/index3.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ReactPullLoad demo3</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
</head>
<body>
<div id="root"></div>
<!--
bundle1.js is demo use document as contianer
bundle2.js is demo use ReactPullLoad root DOM as contianer
-->
<script src="./bundle3.js"></script>
</body>
</html>
================================================
FILE: example/index4.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ReactPullLoad demo4</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
</head>
<body>
<div id="root"></div>
<!--
bundle1.js is demo use document as contianer
bundle2.js is demo use ReactPullLoad root DOM as contianer
-->
<script src="./bundle4.js"></script>
</body>
</html>
================================================
FILE: gulpfile.js
================================================
var gulp = require('gulp');
var webpack = require('webpack');
var clean = require('gulp-clean');
var gutil = require('gulp-util');
// var ftp = require( 'vinyl-ftp' );
var ghPages = require('gulp-gh-pages');
// var deploy = require('./deploy.config.json');
var deploy_remote_path = "/public/17zt/viewer"
var webpack_config_demo = require('./webpack.config.example.js');
var babel = require('gulp-babel');
var less = require('gulp-less');
var path = require('path');
gulp.task('demo:clean', function(){
return gulp.src('./demo', {read: false})
.pipe(clean());
})
gulp.task('demo:file', ['demo:clean'], function(){
return gulp.src(['example/**/*.html','example/README.md'])
.pipe(gulp.dest('demo/'))
})
//编译示例
gulp.task('demo:webpack', ['demo:clean'], function(callback) {
webpack(webpack_config_demo, function (error,status) {
//gulp 异步任务必须明确执行 callback() 否则 gulp 将一直卡住
callback()
});
});
gulp.task('demo:build', ['demo:file', 'demo:webpack']);
//部署示例到自己的测试服务器
// gulp.task('deploy:demo', ['build:demo'], function () {
// deploy.log = gutil.log;
// var conn = ftp.create(deploy);
// return gulp.src('demo/**')
// .pipe(conn.dest(deploy_remote_path))
// })
//部署示例到 gh-pages
gulp.task('deploy:gh-pages', ['demo:build'], function() {
return gulp.src('./demo/**')
.pipe(ghPages());
});
gulp.task("publish:clean", function(){
return gulp.src('./dist', {read: false})
.pipe(clean());
})
gulp.task("publish:ts", ["publish:clean"], function(){
return gulp.src('src/**/*.ts')
.pipe(gulp.dest('dist'));
})
//编译 js 文件
gulp.task('publish:js', ["publish:clean"], function(){
return gulp.src('src/**/*.{js,jsx}')
.pipe(babel({
presets: ["es2015", "stage-1", "react"]
}))
.pipe(gulp.dest('dist'));
})
//编译 less 文件
gulp.task('publish:less', ["publish:clean"], function () {
return gulp.src('src/**/*.less')
.pipe(less({
paths: [ path.join(__dirname, 'less', 'includes') ]
}))
.pipe(gulp.dest('dist'));
});
//发布 css 文件
gulp.task('publish:css', ["publish:clean"], function(){
return gulp.src('src/**/*.css')
.pipe(gulp.dest('dist'))
})
//打包发布 npm
gulp.task('publish', ["publish:clean", 'publish:ts', 'publish:js', 'publish:less']);
gulp.task('demo', ['deploy:demo']);
gulp.task('gh-pages', ['deploy:gh-pages']);
================================================
FILE: lib/FooterNode.js
================================================
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _constants = require('./constants');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var FooterNode = function (_PureComponent) {
_inherits(FooterNode, _PureComponent);
function FooterNode() {
_classCallCheck(this, FooterNode);
return _possibleConstructorReturn(this, (FooterNode.__proto__ || Object.getPrototypeOf(FooterNode)).apply(this, arguments));
}
_createClass(FooterNode, [{
key: 'render',
value: function render() {
var _props = this.props;
var loaderState = _props.loaderState;
var hasMore = _props.hasMore;
var className = 'pull-load-footer-default ' + (hasMore ? "" : "nomore");
return _react2.default.createElement(
'div',
{ className: className },
loaderState === _constants.STATS.loading ? _react2.default.createElement('i', null) : ""
);
}
}]);
return FooterNode;
}(_react.PureComponent);
FooterNode.propTypes = {
loaderState: _react.PropTypes.string.isRequired,
hasMore: _react.PropTypes.bool.isRequired
};
FooterNode.defaultProps = {
loaderState: _constants.STATS.init,
hasMore: true
};
exports.default = FooterNode;
================================================
FILE: lib/HeadNode.js
================================================
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _constants = require('./constants');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var HeadNode = function (_PureComponent) {
_inherits(HeadNode, _PureComponent);
function HeadNode() {
_classCallCheck(this, HeadNode);
return _possibleConstructorReturn(this, (HeadNode.__proto__ || Object.getPrototypeOf(HeadNode)).apply(this, arguments));
}
_createClass(HeadNode, [{
key: 'render',
value: function render() {
var loaderState = this.props.loaderState;
return _react2.default.createElement(
'div',
{ className: 'pull-load-head-default' },
_react2.default.createElement('i', null)
);
}
}]);
return HeadNode;
}(_react.PureComponent);
HeadNode.propTypes = {
loaderState: _react.PropTypes.string.isRequired
};
HeadNode.defaultProps = {
loaderState: _constants.STATS.init
};
exports.default = HeadNode;
================================================
FILE: lib/ReactPullLoad.js
================================================
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _reactDom = require('react-dom');
var _constants = require('./constants');
var _HeadNode = require('./HeadNode');
var _HeadNode2 = _interopRequireDefault(_HeadNode);
var _FooterNode = require('./FooterNode');
var _FooterNode2 = _interopRequireDefault(_FooterNode);
require('./ReactPullLoad.less');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
function addEvent(obj, type, fn) {
if (obj.attachEvent) {
obj['e' + type + fn] = fn;
obj[type + fn] = function () {
obj['e' + type + fn](window.event);
};
obj.attachEvent('on' + type, obj[type + fn]);
} else obj.addEventListener(type, fn, false);
}
function removeEvent(obj, type, fn) {
if (obj.detachEvent) {
obj.detachEvent('on' + type, obj[type + fn]);
obj[type + fn] = null;
} else obj.removeEventListener(type, fn, false);
}
var ReactPullLoad = function (_Component) {
_inherits(ReactPullLoad, _Component);
function ReactPullLoad() {
var _ref;
var _temp, _this, _ret;
_classCallCheck(this, ReactPullLoad);
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = ReactPullLoad.__proto__ || Object.getPrototypeOf(ReactPullLoad)).call.apply(_ref, [this].concat(args))), _this), _this.state = {
pullHeight: 0
}, _this.getScrollTop = function () {
if (_this.defaultConfig.container) {
return _this.defaultConfig.container.scrollTop;
} else {
return 0;
}
}, _this.setScrollTop = function (value) {
if (_this.defaultConfig.container) {
var scrollH = _this.defaultConfig.container.scrollHeight;
if (value < 0) {
value = 0;
}
if (value > scrollH) {
value = scrollH;
}
return _this.defaultConfig.container.scrollTop = value;
} else {
return 0;
}
}, _this.easing = function (distance) {
// t: current time, b: begInnIng value, c: change In value, d: duration
var t = distance;
var b = 0;
var d = screen.availHeight; // 允许拖拽的最大距离
var c = d / 2.5; // 提示标签最大有效拖拽距离
return c * Math.sin(t / d * (Math.PI / 2)) + b;
}, _this.canRefresh = function () {
return [_constants.STATS.refreshing, _constants.STATS.loading].indexOf(_this.props.action) < 0;
}, _this.onPullDownMove = function (data) {
if (!_this.canRefresh()) return false;
var loaderState = void 0,
diff = data[0].touchMoveY - data[0].touchStartY;
if (diff < 0) {
diff = 0;
}
diff = _this.easing(diff);
if (diff > _this.defaultConfig.downEnough) {
loaderState = _constants.STATS.enough;
} else {
loaderState = _constants.STATS.pulling;
}
_this.setState({
pullHeight: diff
});
_this.props.handleAction(loaderState);
}, _this.onPullDownRefresh = function () {
if (!_this.canRefresh()) return false;
if (_this.props.action === _constants.STATS.pulling) {
_this.setState({ pullHeight: 0 });
_this.props.handleAction(_constants.STATS.reset);
} else {
_this.setState({
pullHeight: 0
});
_this.props.handleAction(_constants.STATS.refreshing);
}
}, _this.onPullUpMove = function (data) {
if (!_this.canRefresh()) return false;
// const { hasMore, onLoadMore} = this.props
// if (this.props.hasMore) {
_this.setState({
pullHeight: 0
});
_this.props.handleAction(_constants.STATS.loading);
// }
}, _this.onTouchStart = function (event) {
var targetEvent = event.changedTouches[0];
_this.startX = targetEvent.clientX;
_this.startY = targetEvent.clientY;
}, _this.onTouchMove = function (event) {
var scrollTop = _this.defaultConfig.container.scrollTop,
scrollH = _this.defaultConfig.container.scrollHeight,
conH = _this.defaultConfig.container === document.body ? document.documentElement.clientHeight : _this.defaultConfig.container.offsetHeight,
targetEvent = event.changedTouches[0],
curX = targetEvent.clientX,
curY = targetEvent.clientY,
diffX = curX - _this.startX,
diffY = curY - _this.startY;
//判断垂直移动距离是否大于5 && 横向移动距离小于纵向移动距离
if (Math.abs(diffY) > 5 && Math.abs(diffY) > Math.abs(diffX)) {
//滚动距离小于设定值 &&回调onPullDownMove 函数,并且回传位置值
if (diffY > 5 && scrollTop < _this.defaultConfig.offsetScrollTop) {
//阻止执行浏览器默认动作
event.preventDefault();
_this.onPullDownMove([{
touchStartY: _this.startY,
touchMoveY: curY
}]);
} //滚动距离距离底部小于设定值
else if (diffY < 0 && scrollH - scrollTop - conH < _this.defaultConfig.distanceBottom) {
//阻止执行浏览器默认动作
// event.preventDefault();
_this.onPullUpMove([{
touchStartY: _this.startY,
touchMoveY: curY
}]);
}
}
}, _this.onTouchEnd = function (event) {
var scrollTop = _this.defaultConfig.container.scrollTop,
targetEvent = event.changedTouches[0],
curX = targetEvent.clientX,
curY = targetEvent.clientY,
diffX = curX - _this.startX,
diffY = curY - _this.startY;
//判断垂直移动距离是否大于5 && 横向移动距离小于纵向移动距离
if (Math.abs(diffY) > 5 && Math.abs(diffY) > Math.abs(diffX)) {
if (diffY > 5 && scrollTop < _this.defaultConfig.offsetScrollTop) {
//回调onPullDownRefresh 函数,即满足刷新条件
_this.onPullDownRefresh();
}
}
}, _temp), _possibleConstructorReturn(_this, _ret);
}
//set props default values
_createClass(ReactPullLoad, [{
key: 'componentDidMount',
// container = null;
value: function componentDidMount() {
var _props = this.props;
var isBlockContainer = _props.isBlockContainer;
var offsetScrollTop = _props.offsetScrollTop;
var downEnough = _props.downEnough;
var distanceBottom = _props.distanceBottom;
this.defaultConfig = {
container: isBlockContainer ? (0, _reactDom.findDOMNode)(this) : document.body,
offsetScrollTop: offsetScrollTop,
downEnough: downEnough,
distanceBottom: distanceBottom
};
// console.info("downEnough = ", downEnough, this.defaultConfig.downEnough)
/*
As below reason handle touch event self ( widthout react defualt touch)
Unable to preventDefault inside passive event listener due to target being treated as passive. See https://www.chromestatus.com/features/5093566007214080
*/
addEvent(this.refs.container, "touchstart", this.onTouchStart);
addEvent(this.refs.container, "touchmove", this.onTouchMove);
addEvent(this.refs.container, "touchend", this.onTouchEnd);
}
// 未考虑到 children 及其他 props 改变的情况
// shouldComponentUpdate(nextProps, nextState) {
// if(this.props.action === nextProps.action && this.state.pullHeight === nextState.pullHeight){
// //console.info("[ReactPullLoad] info new action is equal to old action",this.state.pullHeight,nextState.pullHeight);
// return false
// } else{
// return true
// }
// }
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
removeEvent(this.refs.container, "touchstart", this.onTouchStart);
removeEvent(this.refs.container, "touchmove", this.onTouchMove);
removeEvent(this.refs.container, "touchend", this.onTouchEnd);
}
}, {
key: 'componentWillReceiveProps',
value: function componentWillReceiveProps(nextProps) {
var _this2 = this;
if (nextProps.action === _constants.STATS.refreshed) {
setTimeout(function () {
_this2.props.handleAction(_constants.STATS.reset);
}, 1000);
}
}
// 拖拽的缓动公式 - easeOutSine
}, {
key: 'render',
value: function render() {
var _props2 = this.props;
var children = _props2.children;
var action = _props2.action;
var handleAction = _props2.handleAction;
var hasMore = _props2.hasMore;
var className = _props2.className;
var offsetScrollTop = _props2.offsetScrollTop;
var downEnough = _props2.downEnough;
var distanceBottom = _props2.distanceBottom;
var isBlockContainer = _props2.isBlockContainer;
var HeadNode = _props2.HeadNode;
var FooterNode = _props2.FooterNode;
var other = _objectWithoutProperties(_props2, ['children', 'action', 'handleAction', 'hasMore', 'className', 'offsetScrollTop', 'downEnough', 'distanceBottom', 'isBlockContainer', 'HeadNode', 'FooterNode']);
var pullHeight = this.state.pullHeight;
var msgStyle = pullHeight ? {
WebkitTransform: 'translate3d(0, ' + pullHeight + 'px, 0)',
transform: 'translate3d(0, ' + pullHeight + 'px, 0)'
} : null;
var boxClassName = className + ' pull-load state-' + action;
return _react2.default.createElement(
'div',
_extends({}, other, {
className: boxClassName,
ref: 'container' }),
_react2.default.createElement(
'div',
{ className: 'pull-load-body', style: msgStyle },
_react2.default.createElement(
'div',
{ className: 'pull-load-head' },
_react2.default.createElement(HeadNode, { loaderState: action })
),
children,
_react2.default.createElement(
'div',
{ className: 'pull-load-footer' },
_react2.default.createElement(FooterNode, { loaderState: action, hasMore: hasMore })
)
)
);
}
}]);
return ReactPullLoad;
}(_react.Component);
ReactPullLoad.propTypes = {
action: _react.PropTypes.string.isRequired, //用于同步状态
handleAction: _react.PropTypes.func.isRequired, //用于处理状态
hasMore: _react.PropTypes.bool, //是否还有更多内容可加载
offsetScrollTop: _react.PropTypes.number, //必须大于零,使触发刷新往下偏移,隐藏部分顶部内容
downEnough: _react.PropTypes.number, //下拉满足刷新的距离
distanceBottom: _react.PropTypes.number, //距离底部距离触发加载更多
isBlockContainer: _react.PropTypes.bool,
HeadNode: _react.PropTypes.any, //refresh message react dom
FooterNode: _react.PropTypes.any };
ReactPullLoad.defaultProps = {
hasMore: true,
offsetScrollTop: 1,
downEnough: 100,
distanceBottom: 100,
isBlockContainer: false,
className: "",
HeadNode: _HeadNode2.default, //refresh message react dom
FooterNode: _FooterNode2.default };
exports.default = ReactPullLoad;
================================================
FILE: lib/ReactPullLoad.less
================================================
@transition-duration: .2s;
//pull-load container
.pull-load{
position: relative;
overflow-y: scroll;
-webkit-overflow-scrolling: touch;
}
//head load more msg and refreshing UI
.pull-load-head{
position: absolute;
transform: translate3d(0px, -100%, 0px);
width: 100%;
.state-refreshing &,
.state-refreshed &{
position: relative;
transform: none;
}
}
//body container content
.pull-load-body{
// transform: translate3d(0,0,0);// make over the msg-refreshed
position: relative;
.state-refreshing &{
// transform: translate3d(0,@height,0);
transition: transform @transition-duration;
}
.state-refreshed &{
// handle resolve within 1s
// animation: refreshed @transition-duration*5;
}
.state-reset &{
transition: transform @transition-duration;
}
}
/*
* HeadNode default UI
*/
@bg-dark: #EFEFF4;
@height: 3rem;
@fontSize: 12px;
@fontColor: darken(@bg-dark, 40%);// state hint
@btnColor: darken(@bg-dark, 60%);// load more
@pullingMsg: '下拉刷新';
@pullingEnoughMsg: '松开刷新';
@refreshingMsg: '正在刷新...';
@refreshedMsg: '刷新成功';
@loadingMsg: '正在加载...';
@btnLoadMore: '加载更多';
@btnLoadNoMore: '没有更多';
.ui-loading(){
display: inline-block;
vertical-align: middle;
font-size: 1.5rem;
width: 1em;
height: 1em;
border: 2px solid darken(@bg-dark, 30%);
border-top-color: #fff;
border-radius: 100%;
animation: circle .8s infinite linear;
}
.pull-load-head-default{
text-align: center; font-size: @fontSize; line-height: @height; color: @fontColor;
&:after{
.state-pulling &{
content: @pullingMsg
}
.state-pulling.enough &{
content: @pullingEnoughMsg;
}
.state-refreshing &{
content: @refreshingMsg;
}
.state-refreshed &{
content: @refreshedMsg;
}
}
.state-pulling &{
opacity: 1;
// arrow down icon
i{
display: inline-block;
font-size: 2em;
margin-right: .6em;
vertical-align: middle;
height: 1em;
border-left: 1px solid;
position: relative;
transition: transform .3s ease;
&:before,&:after{
content: '';
position: absolute;
font-size: .5em;
width: 1em;
bottom: 0px;
border-top: 1px solid;
}
&:before{
right: 1px;
transform: rotate(50deg);
transform-origin: right;
}
&:after{
left: 0px;
transform: rotate(-50deg);
transform-origin: left;
}
}
}
.state-pulling.enough &{
// arrow up
i{
transform: rotate(180deg);
}
}
.state-refreshing &{
i{
margin-right: 10px;
.ui-loading();
}
}
// 刷新成功提示消息
.state-refreshed &{
opacity: 1;
transition: opacity 1s;
// √ icon
i{
display: inline-block;
box-sizing: content-box;
vertical-align: middle;
margin-right: 10px;
font-size: 20px;
height: 1em;
width: 1em;
border: 1px solid;
border-radius: 100%;
position: relative;
&:before{
content: '';
position: absolute;
top: 3px;
left: 7px;
height: 11px;
width: 5px;
border: solid;
border-width: 0 1px 1px 0;
transform: rotate(40deg);
}
}
}
}
.pull-load-footer-default{
text-align: center; font-size: @fontSize; line-height: @height; color: @fontColor;
&:after{
.state-loading &{
content: @btnLoadMore;
}
}
&.nomore:after{
content: @btnLoadNoMore;
}
.state-loading &{
i{
margin-right: 10px;
.ui-loading();
}
}
}
// loading效果
@keyframes circle {
100% { transform: rotate(360deg); }
}
================================================
FILE: lib/constants.js
================================================
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var STATS = exports.STATS = {
init: '',
pulling: 'pulling',
enough: 'pulling enough',
refreshing: 'refreshing',
refreshed: 'refreshed',
reset: 'reset',
loading: 'loading' // loading more
};
================================================
FILE: lib/index.js
================================================
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = exports.STATS = undefined;
var _constants = require('./constants');
Object.defineProperty(exports, 'STATS', {
enumerable: true,
get: function get() {
return _constants.STATS;
}
});
var _ReactPullLoad = require('./ReactPullLoad');
var _ReactPullLoad2 = _interopRequireDefault(_ReactPullLoad);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
exports.default = _ReactPullLoad2.default;
================================================
FILE: package.json
================================================
{
"name": "react-pullload",
"version": "1.2.0",
"description": "React compopnent pull down refresh and pull up load more",
"main": "./dist/index.js",
"scripts": {
"start": "webpack-dev-server --config webpack.config.js",
"example": "rm -rf ./demo/* & NODE_ENV=development webpack --config webpack.config.example.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/react-ld/react-pullLoad.git"
},
"keywords": [
"react",
"refresh",
"component",
"loadmore"
],
"author": "lidianhao123",
"license": "MIT",
"bugs": {
"url": "https://github.com/react-ld/react-pullLoad/issues"
},
"files": [
"dist",
"example",
"src"
],
"homepage": "https://github.com/react-ld/react-pullLoad#readme",
"devDependencies": {
"autoprefixer": "^6.5.1",
"babel-cli": "^6.16.0",
"babel-core": "^6.17.0",
"babel-loader": "^6.2.5",
"babel-polyfill": "^6.16.0",
"babel-preset-es2015": "^6.16.0",
"babel-preset-react": "^6.16.0",
"babel-preset-stage-1": "^6.16.0",
"css-loader": "^0.25.0",
"gulp": "^3.9.1",
"gulp-babel": "^7.0.0",
"gulp-clean": "^0.3.2",
"gulp-gh-pages": "git@github.com:tekd/gulp-gh-pages.git#update-dependency",
"gulp-less": "^3.3.2",
"gulp-util": "^3.0.8",
"html-webpack-plugin": "^2.22.0",
"less": "^2.7.1",
"less-loader": "^2.2.3",
"postcss": "^5.2.4",
"postcss-loader": "^0.13.0",
"react": "^16.0.0",
"react-dom": "^16.0.0",
"react-hot-loader": "^3.0.0-beta.6",
"style-loader": "^0.13.1",
"webpack": "^2.6.1",
"webpack-dev-server": "^2.4.5"
},
"dependencies": {
"prop-types": "^15.6.0"
}
}
================================================
FILE: postcss.config.js
================================================
module.exports = ({ file, options, env }) => ({
// parser: file.extname === '.sss' ? 'sugarss' : false,
// plugins: {
// 'postcss-import': { root: file.dirname },
// 'postcss-cssnext': options.cssnext ? options.cssnext : false,
// 'autoprefixer': env == 'production' ? options.autoprefixer : false,
// 'cssnano': env === 'production' ? options.cssnano : false
// }
plugins: [ require('autoprefixer')({ browsers: ["Android >= 4", "iOS >= 7"]}) ]
})
================================================
FILE: src/FooterNode.jsx
================================================
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types';
import { STATS } from './constants'
export default class FooterNode extends PureComponent{
static propTypes = {
loaderState: PropTypes.string.isRequired,
hasMore: PropTypes.bool.isRequired
};
static defaultProps = {
loaderState: STATS.init,
hasMore: true
};
render(){
const {
loaderState,
hasMore
} = this.props
let className = `pull-load-footer-default ${hasMore? "" : "nomore"}`
return(
<div className={className}>
{
loaderState === STATS.loading ? <i/> : ""
}
</div>
)
}
}
================================================
FILE: src/HeadNode.jsx
================================================
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types';
import { STATS } from './constants'
export default class HeadNode extends PureComponent{
static propTypes = {
loaderState: PropTypes.string.isRequired,
};
static defaultProps = {
loaderState: STATS.init,
};
render(){
const {
loaderState
} = this.props
return(
<div className="pull-load-head-default">
<i/>
</div>
)
}
}
================================================
FILE: src/ReactPullLoad.jsx
================================================
import React, { Component } from 'react'
import PropTypes from 'prop-types';
import { findDOMNode } from 'react-dom'
import { STATS } from './constants'
import HeadNode from './HeadNode'
import FooterNode from './FooterNode'
function addEvent(obj, type, fn) {
if (obj.attachEvent) {
obj['e' + type + fn] = fn;
obj[type + fn] = function () { obj['e' + type + fn](window.event); }
obj.attachEvent('on' + type, obj[type + fn]);
} else
obj.addEventListener(type, fn, false, {passive: false});
}
function removeEvent(obj, type, fn) {
if (obj.detachEvent) {
obj.detachEvent('on' + type, obj[type + fn]);
obj[type + fn] = null;
} else
obj.removeEventListener(type, fn, false);
}
export default class ReactPullLoad extends Component {
static propTypes = {
action: PropTypes.string.isRequired, //用于同步状态
handleAction: PropTypes.func.isRequired, //用于处理状态
hasMore: PropTypes.bool, //是否还有更多内容可加载
offsetScrollTop: PropTypes.number,//必须大于零,使触发刷新往下偏移,隐藏部分顶部内容
downEnough: PropTypes.number, //下拉满足刷新的距离
distanceBottom: PropTypes.number, //距离底部距离触发加载更多
isBlockContainer: PropTypes.bool,
HeadNode: PropTypes.any, //refresh message react dom
FooterNode: PropTypes.any, //refresh loading react dom
};
//set props default values
static defaultProps = {
hasMore: true,
offsetScrollTop: 1,
downEnough: 100,
distanceBottom: 100,
isBlockContainer: false,
className: "",
HeadNode: HeadNode, //refresh message react dom
FooterNode: FooterNode, //refresh loading react dom
};
state = {
pullHeight: 0
};
// container = null;
componentDidMount() {
const {isBlockContainer, offsetScrollTop, downEnough, distanceBottom} = this.props
this.defaultConfig = {
container: isBlockContainer ? findDOMNode(this) : document.body,
offsetScrollTop: offsetScrollTop,
downEnough: downEnough,
distanceBottom: distanceBottom
};
// console.info("downEnough = ", downEnough, this.defaultConfig.downEnough)
/*
As below reason handle touch event self ( widthout react defualt touch)
Unable to preventDefault inside passive event listener due to target being treated as passive. See https://www.chromestatus.com/features/5093566007214080
*/
addEvent(this.refs.container, "touchstart", this.onTouchStart)
addEvent(this.refs.container, "touchmove", this.onTouchMove)
addEvent(this.refs.container, "touchend", this.onTouchEnd)
}
// 未考虑到 children 及其他 props 改变的情况
// shouldComponentUpdate(nextProps, nextState) {
// if(this.props.action === nextProps.action && this.state.pullHeight === nextState.pullHeight){
// //console.info("[ReactPullLoad] info new action is equal to old action",this.state.pullHeight,nextState.pullHeight);
// return false
// } else{
// return true
// }
// }
componentWillUnmount() {
removeEvent(this.refs.container, "touchstart", this.onTouchStart)
removeEvent(this.refs.container, "touchmove", this.onTouchMove)
removeEvent(this.refs.container, "touchend", this.onTouchEnd)
}
componentWillReceiveProps(nextProps) {
if(nextProps.action === STATS.refreshed){
setTimeout(()=>{
this.props.handleAction(STATS.reset)
},1000)
}
}
getScrollTop = ()=>{
if(this.defaultConfig.container){
if(this.defaultConfig.container === document.body){
return document.documentElement.scrollTop || document.body.scrollTop;
}
return this.defaultConfig.container.scrollTop;
} else{
return 0;
}
}
setScrollTop = (value)=>{
if(this.defaultConfig.container){
let scrollH = this.defaultConfig.container.scrollHeight;
if(value < 0){ value = 0}
if(value > scrollH){ value = scrollH}
return this.defaultConfig.container.scrollTop = value;
} else{
return 0;
}
}
// 拖拽的缓动公式 - easeOutSine
easing = (distance) => {
// t: current time, b: begInnIng value, c: change In value, d: duration
var t = distance;
var b = 0;
var d = screen.availHeight; // 允许拖拽的最大距离
var c = d / 2.5; // 提示标签最大有效拖拽距离
return c * Math.sin(t / d * (Math.PI / 2)) + b;
}
canRefresh = () => {
return [STATS.refreshing, STATS.loading].indexOf(this.props.action) < 0;
}
onPullDownMove = (data) => {
if(!this.canRefresh())return false;
let loaderState, diff = data[0].touchMoveY - data[0].touchStartY;
if (diff < 0) {
diff = 0;
}
diff = this.easing(diff);
if (diff > this.defaultConfig.downEnough) {
loaderState = STATS.enough
} else {
loaderState = STATS.pulling
}
this.setState({
pullHeight: diff,
})
this.props.handleAction(loaderState)
}
onPullDownRefresh = () => {
if(!this.canRefresh())return false;
if (this.props.action === STATS.pulling) {
this.setState({pullHeight: 0})
this.props.handleAction(STATS.reset)
} else {
this.setState({
pullHeight: 0,
})
this.props.handleAction(STATS.refreshing)
}
}
onPullUpMove = (data) => {
if(!this.canRefresh())return false;
// const { hasMore, onLoadMore} = this.props
// if (this.props.hasMore) {
this.setState({
pullHeight: 0,
})
this.props.handleAction(STATS.loading)
// }
}
onTouchStart = (event) => {
var targetEvent = event.changedTouches[0];
this.startX = targetEvent.clientX;
this.startY = targetEvent.clientY;
}
onTouchMove = (event) => {
let scrollTop = this.getScrollTop(),
scrollH = this.defaultConfig.container.scrollHeight,
conH = this.defaultConfig.container === document.body ? document.documentElement.clientHeight : this.defaultConfig.container.offsetHeight,
targetEvent = event.changedTouches[0],
curX = targetEvent.clientX,
curY = targetEvent.clientY,
diffX = curX - this.startX,
diffY = curY - this.startY;
//判断垂直移动距离是否大于5 && 横向移动距离小于纵向移动距离
if (Math.abs(diffY) > 5 && Math.abs(diffY) > Math.abs(diffX)) {
//滚动距离小于设定值 &&回调onPullDownMove 函数,并且回传位置值
if (diffY > 5 && scrollTop < this.defaultConfig.offsetScrollTop) {
//阻止执行浏览器默认动作
event.preventDefault();
this.onPullDownMove([{
touchStartY: this.startY,
touchMoveY: curY
}]);
} //滚动距离距离底部小于设定值
else if (diffY < 0 && (scrollH - scrollTop - conH) < this.defaultConfig.distanceBottom) {
//阻止执行浏览器默认动作
// event.preventDefault();
this.onPullUpMove([{
touchStartY: this.startY,
touchMoveY: curY
}]);
}
}
}
onTouchEnd = (event) => {
let scrollTop = this.getScrollTop(),
targetEvent = event.changedTouches[0],
curX = targetEvent.clientX,
curY = targetEvent.clientY,
diffX = curX - this.startX,
diffY = curY - this.startY;
//判断垂直移动距离是否大于5 && 横向移动距离小于纵向移动距离
if (Math.abs(diffY) > 5 && Math.abs(diffY) > Math.abs(diffX)) {
if (diffY > 5 && scrollTop < this.defaultConfig.offsetScrollTop) {
//回调onPullDownRefresh 函数,即满足刷新条件
this.onPullDownRefresh();
}
}
}
render() {
const {
children,
action,
handleAction,
hasMore,
className,
offsetScrollTop,
downEnough,
distanceBottom,
isBlockContainer,
HeadNode,
FooterNode,
...other
} = this.props
const { pullHeight } = this.state
const msgStyle = pullHeight ? {
WebkitTransform: `translate3d(0, ${pullHeight}px, 0)`,
transform: `translate3d(0, ${pullHeight}px, 0)`
} : null;
const boxClassName = `${className} pull-load state-${action}`;
return (
<div {...other}
className={boxClassName}
ref="container">
<div className="pull-load-body" style={msgStyle}>
<div className="pull-load-head">
<HeadNode loaderState={action}/>
</div>
{ children }
<div className="pull-load-footer">
<FooterNode loaderState={action} hasMore={hasMore}/>
</div>
</div>
</div>
)
}
}
================================================
FILE: src/ReactPullLoad.less
================================================
@transition-duration: .2s;
//pull-load container
.pull-load{
position: relative;
overflow-y: scroll;
-webkit-overflow-scrolling: touch;
}
//head load more msg and refreshing UI
.pull-load-head{
position: absolute;
transform: translate3d(0px, -100%, 0px);
width: 100%;
.state-refreshing &,
.state-refreshed &{
position: relative;
transform: none;
}
}
//body container content
.pull-load-body{
// transform: translate3d(0,0,0);// make over the msg-refreshed
position: relative;
.state-refreshing &{
// transform: translate3d(0,@height,0);
transition: transform @transition-duration;
}
.state-refreshed &{
// handle resolve within 1s
// animation: refreshed @transition-duration*5;
}
.state-reset &{
transition: transform @transition-duration;
}
}
/*
* HeadNode default UI
*/
@bg-dark: #EFEFF4;
@height: 3rem;
@fontSize: 12px;
@fontColor: darken(@bg-dark, 40%);// state hint
@btnColor: darken(@bg-dark, 60%);// load more
@pullingMsg: '下拉刷新';
@pullingEnoughMsg: '松开刷新';
@refreshingMsg: '正在刷新...';
@refreshedMsg: '刷新成功';
@loadingMsg: '正在加载...';
@btnLoadMore: '加载更多';
@btnLoadNoMore: '没有更多';
.ui-loading(){
display: inline-block;
vertical-align: middle;
font-size: 1.5rem;
width: 1em;
height: 1em;
border: 2px solid darken(@bg-dark, 30%);
border-top-color: #fff;
border-radius: 100%;
animation: circle .8s infinite linear;
}
.pull-load-head-default{
text-align: center; font-size: @fontSize; line-height: @height; color: @fontColor;
&:after{
.state-pulling &{
content: @pullingMsg
}
.state-pulling.enough &{
content: @pullingEnoughMsg;
}
.state-refreshing &{
content: @refreshingMsg;
}
.state-refreshed &{
content: @refreshedMsg;
}
}
.state-pulling &{
opacity: 1;
// arrow down icon
i{
display: inline-block;
font-size: 2em;
margin-right: .6em;
vertical-align: middle;
height: 1em;
border-left: 1px solid;
position: relative;
transition: transform .3s ease;
&:before,&:after{
content: '';
position: absolute;
font-size: .5em;
width: 1em;
bottom: 0px;
border-top: 1px solid;
}
&:before{
right: 1px;
transform: rotate(50deg);
transform-origin: right;
}
&:after{
left: 0px;
transform: rotate(-50deg);
transform-origin: left;
}
}
}
.state-pulling.enough &{
// arrow up
i{
transform: rotate(180deg);
}
}
.state-refreshing &{
i{
margin-right: 10px;
.ui-loading();
}
}
// 刷新成功提示消息
.state-refreshed &{
opacity: 1;
transition: opacity 1s;
// √ icon
i{
display: inline-block;
box-sizing: content-box;
vertical-align: middle;
margin-right: 10px;
font-size: 20px;
height: 1em;
width: 1em;
border: 1px solid;
border-radius: 100%;
position: relative;
&:before{
content: '';
position: absolute;
top: 3px;
left: 7px;
height: 11px;
width: 5px;
border: solid;
border-width: 0 1px 1px 0;
transform: rotate(40deg);
}
}
}
}
.pull-load-footer-default{
text-align: center; font-size: @fontSize; line-height: @height; color: @fontColor;
&:after{
.state-loading &{
content: @btnLoadMore;
}
}
&.nomore:after{
content: @btnLoadNoMore;
}
.state-loading &{
i{
margin-right: 10px;
.ui-loading();
}
}
}
// loading效果
@keyframes circle {
100% { transform: rotate(360deg); }
}
================================================
FILE: src/constants.js
================================================
export const STATS = {
init: '',
pulling: 'pulling',
enough: 'pulling enough',
refreshing: 'refreshing',
refreshed: 'refreshed',
reset: 'reset',
loading: 'loading' // loading more
};
================================================
FILE: src/index.d.ts
================================================
import * as React from "react";
declare enum STATS {
init = "",
pulling = "pulling",
enough = "pulling enough",
refreshing = "refreshing",
refreshed = "refreshed",
reset = "reset",
loading = "loading" // loading more
}
export interface PullLoadProps {
action: STATS; //用于同步状态
handleAction: (action: STATS) => void; //用于处理状态
hasMore: boolean; //是否还有更多内容可加载
offsetScrollTop?: number; //必须大于零,使触发刷新往下偏移,隐藏部分顶部内容
downEnough?: number; //下拉满足刷新的距离
distanceBottom?: number; //距离底部距离触发加载更多
isBlockContainer?: boolean;
HeadNode?: React.ReactNode | string; //refresh message react dom
FooterNode?: React.ReactNode | string; //refresh loading react dom
children: React.ReactChild; // 子组件
}
export default class ReactPullLoad extends React.Component<
PullLoadProps,
any
> {}
================================================
FILE: src/index.js
================================================
// import { STATS as _STATS } from 'constants'
// export const STATS = _STATS
// export default ReactPullLoad
export { STATS } from './constants'
export default from './ReactPullLoad'
================================================
FILE: webpack.config.example.js
================================================
var path = require("path");
var webpack = require("webpack");
var HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
context: path.resolve(__dirname, "example"), // string(绝对路径!)
devtool: "eval",
cache: true,
entry: {
bundle1: ["babel-polyfill", "./App1.jsx"],
bundle2: ["babel-polyfill", "./App2.jsx"],
bundle3: ["babel-polyfill", "./App3.jsx"],
bundle4: ["babel-polyfill", "./App4.jsx"]
},
output: {
path: path.join(__dirname, "demo/"),
filename: "[name].js"
},
plugins: [
// new webpack.optimize.OccurenceOrderPlugin(),
new webpack.DefinePlugin({
"process.env": {
NODE_ENV: JSON.stringify("production")
}
}),
new webpack.NamedModulesPlugin(),
// 当模块热替换(HMR)时在浏览器控制台输出对用户更友好的模块名字信息
new webpack.optimize.UglifyJsPlugin({
sourceMap: true,
compress: {
warnings: true
}
})
// new HtmlWebpackPlugin({ template: 'index.html' })
],
resolve: {
extensions: [".js", ".jsx"],
modules: ["node_modules", path.resolve(__dirname, "src")]
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
loader: "babel-loader",
exclude: /node_modules/,
include: __dirname,
options: {
presets: [["es2015", { modules: false }], "stage-1", "react"]
}
},
{
test: /\.css$/,
use: [
"style-loader",
"css-loader",
{ loader: "postcss-loader", options: { config: { path: "./postcss.config.js" } } }
]
},
{
test: /\.less/,
use: [
"style-loader",
"css-loader",
{ loader: "postcss-loader", options: { config: { path: "./postcss.config.js" } } },
"less-loader"
]
},
{
test: /\.(gif|jpg|png|woff|svg|eot|ttf)$/,
use: [{ loader: "file-loader" }]
}
]
}
};
================================================
FILE: webpack.config.js
================================================
var webpack = require("webpack");
var HtmlWebpackPlugin = require("html-webpack-plugin");
var path = require("path");
var port = 3010;
var demoNum = 3;
module.exports = {
context: path.resolve(__dirname, "example"), // string(绝对路径!)
devtool: "eval",
cache: true,
entry: [
"react-hot-loader/patch",
// 开启 React 代码的模块热替换(HMR)
"webpack-dev-server/client?http://0.0.0.0:" + port,
"webpack/hot/only-dev-server",
"./App"+demoNum+".jsx"
],
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(),
// 当模块热替换(HMR)时在浏览器控制台输出对用户更友好的模块名字信息
new webpack.optimize.UglifyJsPlugin({
sourceMap: true,
compress: {
warnings: true
}
}),
new HtmlWebpackPlugin({
title: "Custom template",
template: "./index"+demoNum+".html", // Load a custom template (ejs by default see the FAQ for details)
hash: true,
filename: "./index.html"
})
],
resolve: {
modules: ["node_modules", path.resolve(__dirname, "src")],
extensions: [".js", ".jsx"]
},
devServer: {
hot: true,
// 开启服务器的模块热替换(HMR)
host: "0.0.0.0",
port: port
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
loader: "babel-loader",
exclude: /node_modules/,
include: __dirname,
options: {
presets: [["es2015", { modules: false }], "stage-1", "react"],
plugins: [
"react-hot-loader/babel"
// 开启 React 代码的模块热替换(HMR)
]
}
},
{
test: /\.css$/,
use: [
"style-loader",
"css-loader",
{ loader: "postcss-loader", options: { config: { path: "./postcss.config.js" } } }
]
},
{
test: /\.less/,
use: [
"style-loader",
"css-loader",
{ loader: "postcss-loader", options: { config: { path: "./postcss.config.js" } } },
"less-loader"
]
},
{
test: /\.(gif|jpg|png|woff|svg|eot|ttf)$/,
use: [{ loader: "file-loader" }]
}
]
}
};
gitextract_gef5ggiu/ ├── .babelrc ├── .gitignore ├── README-cn.md ├── README.md ├── example/ │ ├── App.css │ ├── App1.jsx │ ├── App2.jsx │ ├── App3.jsx │ ├── App4.jsx │ ├── README.md │ ├── index1.html │ ├── index2.html │ ├── index3.html │ └── index4.html ├── gulpfile.js ├── lib/ │ ├── FooterNode.js │ ├── HeadNode.js │ ├── ReactPullLoad.js │ ├── ReactPullLoad.less │ ├── constants.js │ └── index.js ├── package.json ├── postcss.config.js ├── src/ │ ├── FooterNode.jsx │ ├── HeadNode.jsx │ ├── ReactPullLoad.jsx │ ├── ReactPullLoad.less │ ├── constants.js │ ├── index.d.ts │ └── index.js ├── webpack.config.example.js └── webpack.config.js
SYMBOL INDEX (53 symbols across 13 files)
FILE: example/App1.jsx
class App (line 29) | class App extends Component{
method constructor (line 30) | constructor(){
method render (line 97) | render(){
FILE: example/App2.jsx
class App (line 28) | class App extends Component{
method constructor (line 29) | constructor(){
method render (line 85) | render(){
FILE: example/App3.jsx
class HeadNode (line 16) | class HeadNode extends PureComponent{
method render (line 26) | render(){
class FooterNode (line 50) | class FooterNode extends PureComponent{
method render (line 62) | render(){
class App (line 100) | class App extends Component{
method constructor (line 101) | constructor(){
method render (line 157) | render(){
FILE: example/App4.jsx
class App (line 28) | class App extends Component{
method constructor (line 29) | constructor(){
method render (line 80) | render(){
FILE: lib/FooterNode.js
function defineProperties (line 7) | function defineProperties(target, props) { for (var i = 0; i < props.len...
function _interopRequireDefault (line 15) | function _interopRequireDefault(obj) { return obj && obj.__esModule ? ob...
function _classCallCheck (line 17) | function _classCallCheck(instance, Constructor) { if (!(instance instanc...
function _possibleConstructorReturn (line 19) | function _possibleConstructorReturn(self, call) { if (!self) { throw new...
function _inherits (line 21) | function _inherits(subClass, superClass) { if (typeof superClass !== "fu...
function FooterNode (line 26) | function FooterNode() {
FILE: lib/HeadNode.js
function defineProperties (line 7) | function defineProperties(target, props) { for (var i = 0; i < props.len...
function _interopRequireDefault (line 15) | function _interopRequireDefault(obj) { return obj && obj.__esModule ? ob...
function _classCallCheck (line 17) | function _classCallCheck(instance, Constructor) { if (!(instance instanc...
function _possibleConstructorReturn (line 19) | function _possibleConstructorReturn(self, call) { if (!self) { throw new...
function _inherits (line 21) | function _inherits(subClass, superClass) { if (typeof superClass !== "fu...
function HeadNode (line 26) | function HeadNode() {
FILE: lib/ReactPullLoad.js
function defineProperties (line 9) | function defineProperties(target, props) { for (var i = 0; i < props.len...
function _interopRequireDefault (line 29) | function _interopRequireDefault(obj) { return obj && obj.__esModule ? ob...
function _objectWithoutProperties (line 31) | function _objectWithoutProperties(obj, keys) { var target = {}; for (var...
function _classCallCheck (line 33) | function _classCallCheck(instance, Constructor) { if (!(instance instanc...
function _possibleConstructorReturn (line 35) | function _possibleConstructorReturn(self, call) { if (!self) { throw new...
function _inherits (line 37) | function _inherits(subClass, superClass) { if (typeof superClass !== "fu...
function addEvent (line 39) | function addEvent(obj, type, fn) {
function removeEvent (line 48) | function removeEvent(obj, type, fn) {
function ReactPullLoad (line 58) | function ReactPullLoad() {
FILE: lib/index.js
function _interopRequireDefault (line 21) | function _interopRequireDefault(obj) { return obj && obj.__esModule ? ob...
FILE: src/FooterNode.jsx
class FooterNode (line 6) | class FooterNode extends PureComponent{
method render (line 18) | render(){
FILE: src/HeadNode.jsx
class HeadNode (line 6) | class HeadNode extends PureComponent{
method render (line 16) | render(){
FILE: src/ReactPullLoad.jsx
function addEvent (line 9) | function addEvent(obj, type, fn) {
function removeEvent (line 17) | function removeEvent(obj, type, fn) {
class ReactPullLoad (line 25) | class ReactPullLoad extends Component {
method componentDidMount (line 56) | componentDidMount() {
method componentWillUnmount (line 84) | componentWillUnmount() {
method componentWillReceiveProps (line 90) | componentWillReceiveProps(nextProps) {
method render (line 235) | render() {
FILE: src/constants.js
constant STATS (line 2) | const STATS = {
FILE: src/index.d.ts
type STATS (line 2) | enum STATS {
type PullLoadProps (line 13) | interface PullLoadProps {
class ReactPullLoad (line 26) | class ReactPullLoad extends React.Component<
Condensed preview — 32 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (88K chars).
[
{
"path": ".babelrc",
"chars": 47,
"preview": "{\n \"presets\": [\"es2015\", \"stage-1\", \"react\"]\n}"
},
{
"path": ".gitignore",
"chars": 943,
"preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npackage-lock.json\n\n# Runtime data\npids\n*.pid\n*.seed\n*.p"
},
{
"path": "README-cn.md",
"chars": 6477,
"preview": "# [English](./README.md)\n\n# [react-pullLoad](https://github.com/react-ld/react-pullLoad)\n\nReact 版本的 [pullLoad](https://g"
},
{
"path": "README.md",
"chars": 6973,
"preview": "# [中文](./README-cn.md)\n\n# [react-pullLoad](https://github.com/react-ld/react-pullLoad)\n\nRefreshing and Loading more comp"
},
{
"path": "example/App.css",
"chars": 1022,
"preview": "html,body{margin: 0; padding: 0;}\nli{font-size: 20px; width: 100%;list-style: none;}\nimg{width: 100%;}\ndiv, .test-ul, p{"
},
{
"path": "example/App1.jsx",
"chars": 4235,
"preview": "\nimport React, { Component, PureComponent } from 'react'\nimport PropTypes from 'prop-types';\nimport { render } from 'rea"
},
{
"path": "example/App2.jsx",
"chars": 3277,
"preview": "\nimport React, { Component, PureComponent } from 'react'\nimport PropTypes from 'prop-types';\nimport { render } from 'rea"
},
{
"path": "example/App3.jsx",
"chars": 4965,
"preview": "\nimport React, { Component, PureComponent } from 'react'\nimport PropTypes from 'prop-types';\nimport { render } from 'rea"
},
{
"path": "example/App4.jsx",
"chars": 3549,
"preview": "\nimport React, { Component, PureComponent } from 'react'\nimport PropTypes from 'prop-types';\nimport { render } from 'rea"
},
{
"path": "example/README.md",
"chars": 428,
"preview": "# 示例\n[demo1](https://react-ld.github.io/react-pullLoad/index1.html) ReactPullLoad 根节点 DOM 作为容器\n\n[demo2](https://react-ld"
},
{
"path": "example/index1.html",
"chars": 450,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>ReactPullLoad demo1</title>\n <meta name"
},
{
"path": "example/index2.html",
"chars": 450,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>ReactPullLoad demo2</title>\n <meta name"
},
{
"path": "example/index3.html",
"chars": 450,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>ReactPullLoad demo3</title>\n <meta name"
},
{
"path": "example/index4.html",
"chars": 450,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>ReactPullLoad demo4</title>\n <meta name"
},
{
"path": "gulpfile.js",
"chars": 2308,
"preview": "var gulp = require('gulp');\nvar webpack = require('webpack');\nvar clean = require('gulp-clean');\nvar gutil = require('gu"
},
{
"path": "lib/FooterNode.js",
"chars": 2815,
"preview": "'use strict';\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nvar _createClass = function () { functi"
},
{
"path": "lib/HeadNode.js",
"chars": 2555,
"preview": "'use strict';\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nvar _createClass = function () { functi"
},
{
"path": "lib/ReactPullLoad.js",
"chars": 12808,
"preview": "'use strict';\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nvar _extends = Object.assign || functio"
},
{
"path": "lib/ReactPullLoad.less",
"chars": 4279,
"preview": "\n@transition-duration: .2s;\n\n//pull-load container\n.pull-load{\n position: relative;\n overflow-y: scroll;\n -webk"
},
{
"path": "lib/constants.js",
"chars": 284,
"preview": "'use strict';\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nvar STATS = exports.STATS = {\n init: ''"
},
{
"path": "lib/index.js",
"chars": 544,
"preview": "'use strict';\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = exports.STATS = undefi"
},
{
"path": "package.json",
"chars": 1753,
"preview": "{\n \"name\": \"react-pullload\",\n \"version\": \"1.2.0\",\n \"description\": \"React compopnent pull down refresh and pull up loa"
},
{
"path": "postcss.config.js",
"chars": 471,
"preview": "module.exports = ({ file, options, env }) => ({\n // parser: file.extname === '.sss' ? 'sugarss' : false,\n // plugins: "
},
{
"path": "src/FooterNode.jsx",
"chars": 661,
"preview": "\nimport React, { PureComponent } from 'react'\nimport PropTypes from 'prop-types';\nimport { STATS } from './constants'\n\ne"
},
{
"path": "src/HeadNode.jsx",
"chars": 467,
"preview": "\nimport React, { PureComponent } from 'react'\nimport PropTypes from 'prop-types';\nimport { STATS } from './constants'\n\ne"
},
{
"path": "src/ReactPullLoad.jsx",
"chars": 8188,
"preview": "\nimport React, { Component } from 'react'\nimport PropTypes from 'prop-types';\nimport { findDOMNode } from 'react-dom'\nim"
},
{
"path": "src/ReactPullLoad.less",
"chars": 4279,
"preview": "\n@transition-duration: .2s;\n\n//pull-load container\n.pull-load{\n position: relative;\n overflow-y: scroll;\n -webk"
},
{
"path": "src/constants.js",
"chars": 198,
"preview": "\nexport const STATS = {\n init: '',\n pulling: 'pulling',\n enough: 'pulling enough',\n refreshing: 'refreshing',\n refr"
},
{
"path": "src/index.d.ts",
"chars": 804,
"preview": "import * as React from \"react\";\ndeclare enum STATS {\n init = \"\",\n pulling = \"pulling\",\n enough = \"pulling enough\",\n "
},
{
"path": "src/index.js",
"chars": 184,
"preview": "\n// import { STATS as _STATS } from 'constants'\n// export const STATS = _STATS\n// export default ReactPullLoad\nexport { "
},
{
"path": "webpack.config.example.js",
"chars": 1911,
"preview": "var path = require(\"path\");\nvar webpack = require(\"webpack\");\nvar HtmlWebpackPlugin = require(\"html-webpack-plugin\");\n\nm"
},
{
"path": "webpack.config.js",
"chars": 2102,
"preview": "var webpack = require(\"webpack\");\nvar HtmlWebpackPlugin = require(\"html-webpack-plugin\");\nvar path = require(\"path\");\nva"
}
]
About this extraction
This page contains the full source code of the react-ld/react-pullLoad GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 32 files (78.4 KB), approximately 22.0k tokens, and a symbol index with 53 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.