Commit 99ea9ec0 authored by 李勇諭's avatar 李勇諭

implement pull to refresh

parents
.idea
dist
node_modules
yarn.lock
\ No newline at end of file
/node_modules
/public
/src
dist/index.d.ts
tsconfig.json
tslint.json
webpack.config.js
yarn.lock
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>React js pull to refresh</title>
</head>
<body>
<div id="sample"></div>
<script src="bundle.js"></script>
</body>
</html>
\ No newline at end of file
import * as React from "react";
import {autobind} from "core-decorators";
export interface PullToRefreshProps {
pullDownContent: JSX.Element;
releaseContent: JSX.Element;
refreshContent: JSX.Element;
pullDownThreshold: number;
onRefresh: () => Promise<any>;
}
export interface PullToRefreshState {
pullToRefreshThresholdBreached: boolean;
maxPullDownDistance: number;
onRefreshing: boolean;
}
export class PullToRefresh extends React.Component<PullToRefreshProps, PullToRefreshState> {
private container: HTMLElement;
@autobind
private containerRef(container: HTMLElement) {
this.container = container;
}
private pullDown: HTMLElement;
@autobind
private pullDownRef(pullDown: HTMLElement) {
this.pullDown = pullDown;
const maxPullDownDistance = this.pullDown.firstChild["getBoundingClientRect"]().height;
this.setState({maxPullDownDistance});
}
private dragging = false;
private startY = 0;
private currentY = 0;
constructor(props: Readonly<PullToRefreshProps>) {
super(props);
this.state = {
pullToRefreshThresholdBreached: false,
maxPullDownDistance: 0,
onRefreshing: false,
};
}
public componentDidMount(): void {
this.container.addEventListener("touchstart", this.onTouchStart);
this.container.addEventListener("touchmove", this.onTouchMove);
this.container.addEventListener("touchend", this.onEnd);
this.container.addEventListener("mousedown", this.onTouchStart);
this.container.addEventListener("mousemove", this.onTouchMove);
this.container.addEventListener("mouseup", this.onEnd);
}
public componentWillUnmount(): void {
this.container.removeEventListener("touchstart", this.onTouchStart);
this.container.removeEventListener("touchmove", this.onTouchMove);
this.container.removeEventListener("touchend", this.onEnd);
this.container.removeEventListener("mousedown", this.onTouchStart);
this.container.removeEventListener("mousemove", this.onTouchMove);
this.container.removeEventListener("mouseup", this.onEnd);
}
@autobind
private onTouchStart(e: TouchEvent) {
this.dragging = true;
this.startY = e["pageY"] || e.touches[0].pageY;
this.currentY = this.startY;
this.container.style["willChange"] = "transform";
this.container.style.transition = "transform 0.2s cubic-bezier(0,0,0.31,1)";
}
@autobind
private onTouchMove(e: TouchEvent) {
if (!this.dragging) {
return;
}
this.currentY = e["pageY"] || e.touches[0].pageY;
if (this.currentY < this.startY) {
return;
}
if ((this.currentY - this.startY) >= this.props.pullDownThreshold) {
this.setState({
pullToRefreshThresholdBreached: true,
});
}
if (this.currentY - this.startY > this.state.maxPullDownDistance * 1.5) {
return;
}
this.container.style.overflow = "visible";
this.container.style.transform = `translate3d(0px, ${this.currentY - this.startY}px, 0px)`;
}
@autobind
private onEnd() {
this.setState({
pullToRefreshThresholdBreached: false,
onRefreshing: true,
}, () => {
this.props.onRefresh().then(() => {
this.setState({onRefreshing: false});
requestAnimationFrame(() => {
this.container.style.overflow = "auto";
this.container.style.transform = "none";
this.container.style["willChange"] = "none";
});
this.dragging = false;
this.startY = 0;
this.currentY = 0;
});
});
}
private renderPullDownContent() {
const {releaseContent, pullDownContent, refreshContent} = this.props;
const {onRefreshing, pullToRefreshThresholdBreached} = this.state;
return onRefreshing ? refreshContent : pullToRefreshThresholdBreached ? releaseContent : pullDownContent;
}
public render() {
const {maxPullDownDistance} = this.state;
const containerStyle: React.CSSProperties = {
height: "auto",
overflow: "auto",
WebkitOverflowScrolling: "touch",
};
const pullContentStyles: React.CSSProperties = {
position: "absolute",
left: 0,
right: 0,
top: (-1 * maxPullDownDistance),
};
return (
<div ref={this.containerRef} style={containerStyle}>
<div ref={this.pullDownRef} style={{position: "relative"}}>
<div style={pullContentStyles}>
{this.renderPullDownContent()}
</div>
</div>
{this.props.children}
</div>
);
}
}
/* tslint:disable:no-implicit-dependencies */
import * as React from "react";
import {render} from "react-dom";
import {AppContainer} from "react-hot-loader";
import {PullToRefresh} from "./PullToRefresh";
const onRefresh = () => {
return new Promise((reslove) => {
setTimeout(reslove, 3000);
});
};
const renderMasterLayout = () => {
const root = document.getElementById("sample");
render(
<AppContainer>
<div>
<div>Header</div>
<PullToRefresh
pullDownContent={<span>pullDownContent</span>}
releaseContent={<span>releaseContent</span>}
refreshContent={<span>refreshing</span>}
pullDownThreshold={50}
onRefresh={onRefresh}
>
<div style={{backgroundColor: "green", height: "300px", color: "white", textAlign: "center"}}>
PullToRefresh
</div>
</PullToRefresh>
</div>
</AppContainer>,
root,
);
};
renderMasterLayout();
// Hot Module Replacement API
declare const module: { hot: any };
if (module.hot) {
module.hot.accept("./PullToRefresh", () => {
renderMasterLayout();
});
}
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"sourceMap": true,
"noImplicitAny": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"declaration": true,
"declarationDir": "./dist/",
"lib": ["es5", "es6", "dom", "ScriptHost"],
"jsx": "react",
"outDir": "./dist/"
},
"exclude": [
"node_modules"
]
}
\ No newline at end of file
{
"extends": ["tslint:latest", "tslint-react"],
"rules": {
"no-parameter-properties": true,
"no-empty-interface": false,
"no-empty": false,
"interface-name": false,
"no-reference": false,
"no-console": false,
"no-var-requires": true,
"no-unused-expression": false,
"no-duplicate-imports": false,
"no-object-literal-type-assertion": false,
"no-string-literal": false,
"object-literal-sort-keys": false,
"object-literal-key-quotes": false,
"max-line-length": [true, 150],
"ordered-imports": false,
"member-ordering": false
}
}
\ No newline at end of file
const webpack = require('webpack');
const resolve = require('path').resolve;
module.exports = {
resolve: {
extensions: [".webpack.js", ".web.js", ".ts", ".tsx", ".js", ".jsx"],
alias: {
"handlebars" : 'handlebars/dist/handlebars.js'
}
},
entry: [
"react-hot-loader/patch", // activate HMR for React
"webpack-dev-server/client?http://localhost:8080/",
"webpack/hot/only-dev-server",
"./src/index.tsx"
],
output: {
filename: "bundle.js",
path: resolve(__dirname, "public")
},
// Enable sourcemaps for debugging webpack's output.
devtool: "source-map",
devServer: {
historyApiFallback: true,
hot: true, // enable HMR on the server
contentBase: resolve(__dirname, "public"), // match the output path
publicPath: "/" // match the output `publicPath`
},
module: {
rules: [
{ test: /\.js$/, loader: "source-map-loader"},
{ test: /\.tsx?$/, loader: "awesome-typescript-loader"}
]
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin()
],
node:{
fs: 'empty',
net: 'empty',
tls: 'empty'
}
};
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment