1. import React from 'react'
  2. import PropTypes from 'prop-types'
  3. import {findDOMNode} from 'react-dom'
  4. import Component from '../utils/Component'
  5. import classnames from 'classnames'
  6. import {setPhPrefix, getClientHeight, getScrollTop, getDocumentHeight, preventDefault} from '../utils/Tool'
  7. import Logger from '../utils/logger'
  8.  
  9. import Button from '../button'
  10. import Icon from '../icon'
  11.  
  12. import '../style'
  13. import 'phoenix-styles/less/modules/pullup.less'
  14.  
  15. /**
  16. * 加载更多组件<br/>
  17. * - 书写时PullDown组件在可加载列表的前面。
  18. * - 可通过loadCallback设置下拉的执行回调。
  19. * - 如果当前列表存在自定义的滚动条,需要通过getTarget传递滚动的目标,且滚动元素的子元素必须只有一个。
  20. * - 只有getTarget的元素(默认window)滚到最顶端的时候,才能触发下拉。
  21. *
  22. * 主要属性和接口:
  23. * - loadCallback:点击按钮加载或滑到底部自动加载的回调函数。
  24. * - getTarget: 如果当前列表存在自定义的滚动条,需要传递滚动的目标。
  25. *
  26. * 示例:
  27. * ```code
  28. * <div style={{height:'300px',overflow:'auto'}} ref={(list)=>this.list=list}> // 用到getTarget需要保证只有一个子元素,包裹住滚动的所有内容
  29. * <div>
  30. * <List>...</List> // 可加载列表的位置
  31. * <PullDown loadCallback={this.loadCallback.bind(this)}
  32. * getTarget={()=>{return list;}} />
  33. * </div>
  34. * </div>
  35. * ```
  36. *
  37. * @class PullDown
  38. * @module 操作类组件
  39. * @extends Component
  40. * @constructor
  41. * @since 3.3.0
  42. * @demo pulldown|pulldown.js {展示}
  43. * @show true
  44. * */
  45. const MAX_HEIGHT = 800,
  46. MAX_DISTANCE = 200/2
  47.  
  48. export default class PullDown extends Component{
  49. static propTypes = {
  50. /**
  51. * 样式前缀
  52. * @property classPrefix
  53. * @type String
  54. * @default 'pulldown'
  55. * */
  56. classPrefix: PropTypes.string,
  57. /**
  58. * 滑到底部自动加载的回调函数,用户在该函数内自定义请求
  59. * @method loadCallback
  60. * @type Function
  61. * @default null
  62. **/
  63. loadCallback: PropTypes.func,
  64. /**
  65. * 如果当前列表存在自定义的滚动条,需要传递滚动的目标
  66. * @method getTarget
  67. * @type Function
  68. * @default null
  69. * @return {object} 目标元素的ref
  70. **/
  71. getTarget: PropTypes.func
  72. }
  73.  
  74. static defaultProps ={
  75. classPrefix:'pulldown',
  76. classMapping : {}
  77. }
  78.  
  79. constructor(props,context){ // 记得做数据没有触底的判断
  80. super(props,context)
  81. new Logger('PullDown')
  82.  
  83. this.touchTop = true
  84. this.distanceY = 0
  85. this.start = false
  86.  
  87. this.scrollHandle = this.scrollHandle.bind(this)
  88. this.scrollElem = window
  89. }
  90.  
  91. scrollHandle(e){
  92. let {getTarget} = this.props,
  93. offsetTop = this.nextElem.offsetTop,
  94. scrollTop = getScrollTop()
  95. if(getTarget){
  96. scrollTop = target.scrollTop
  97. }
  98. // console.log('offsetTop', offsetTop)
  99. // console.log('scrollTop', scrollTop)
  100.  
  101. if(scrollTop>0){
  102. this.touchTop = false
  103. }else{
  104. this.touchTop = true
  105. }
  106. }
  107.  
  108. componentDidMount(){
  109. let pullDownElem = findDOMNode(this.pullDown)
  110. this.dragElem = pullDownElem.parentNode
  111. this.nextElem = pullDownElem.nextElementSibling
  112. this.addClass(this.dragElem, 'animated')
  113. // this.addClass(this.nextElem, 'hardware')
  114.  
  115. this.dragEventHandle(this.dragElem)
  116.  
  117. if(this.props.getTarget){
  118. setTimeout(()=>{
  119. this.scrollElem = this.props.getTarget()
  120. this.scrollElem.addEventListener('scroll', this.scrollHandle, false)
  121. },0);
  122. }else{
  123. this.scrollElem.addEventListener('scroll', this.scrollHandle, false)
  124. }
  125. }
  126.  
  127. dragEventHandle(elem){
  128. this.touchStartHandle = this.touchStartHandle.bind(this)
  129. elem.addEventListener('touchstart', this.touchStartHandle, false)
  130.  
  131. this.touchMoveHandle = this.touchMoveHandle.bind(this)
  132. elem.addEventListener('touchmove', this.touchMoveHandle, false)
  133.  
  134. this.touchEndHandle = this.touchEndHandle.bind(this)
  135. elem.addEventListener('touchend', this.touchEndHandle, false)
  136.  
  137. this.touchCancelHandle = this.touchCancelHandle.bind(this)
  138. elem.addEventListener('touchcancel', this.touchCancelHandle, false)
  139. }
  140.  
  141. loadCallback(){
  142. let {loadCallback} = this.props
  143. if(loadCallback) loadCallback()
  144. }
  145.  
  146. touchStartHandle(e){
  147. if(!this.touchTop) return
  148. this.start = true
  149. this.distanceY = 0
  150. this.starY = event.touches[0].pageY
  151. }
  152.  
  153. touchMoveHandle(e){
  154. if(!this.touchTop || !this.start) return
  155.  
  156. this.moveY = event.touches[0].pageY
  157. this.distanceY = this.moveY - this.starY
  158. if(this.distanceY<=0) return
  159. else preventDefault(e)
  160. this.distanceY = Math.abs(this.distanceY)
  161. if(this.distanceY >= MAX_DISTANCE) this.distanceY = MAX_DISTANCE
  162.  
  163. this.transform = Math.min(1, MAX_HEIGHT/this.distanceY) * Math.min(MAX_HEIGHT, this.distanceY)
  164. this.dragElem.style.marginTop = this.transform+'px'
  165. }
  166.  
  167. touchEndHandle(){
  168. if(!this.touchTop || !this.start) return
  169. this.starY = this.moveY
  170.  
  171. this.resetDragElem()
  172. if(Math.abs(this.distanceY) <= 80 || this.distanceY<=0) return
  173.  
  174. this.loadCallback()
  175. this.start = false
  176. }
  177.  
  178. touchCancelHandle(){
  179. this.resetDragElem()
  180. }
  181.  
  182. resetDragElem(){
  183. this.dragElem.style.marginTop = '0'
  184. }
  185.  
  186. componentWillUnmount(){
  187. this.scrollElem.removeEventListener('scroll', this.scrollHandle, false)
  188.  
  189. this.dragElem.removeEventListener('touchstart', this.touchStartHandle, false)
  190. this.dragElem.removeEventListener('touchmove', this.touchMoveHandle, false)
  191. this.dragElem.removeEventListener('touchend', this.touchEndHandle, false)
  192. }
  193.  
  194. renderPullDown(){
  195. return (
  196. <div {...this.otherProps}
  197. ref={(pullDown)=>{this.pullDown=pullDown}}
  198. className={classnames(this.getProperty(true), this.props.className)}
  199. >
  200. <div className={setPhPrefix('pulldown-tip')}>
  201. <Icon className='gfs-icon-loading' phIcon='loading-gray' phSize='sm' />
  202. </div>
  203. </div>
  204. )
  205. }
  206.  
  207. render(){
  208. return this.renderPullDown()
  209. }
  210. }