基於antd封裝橫向時間軸;

萬事開頭難,一直都想寫博客,分享下本身的東西,科室無奈於工做比較忙,也不知道寫點啥好,太淺了吧,以爲沒意思,太深吧,我也不會,恰好公司最近要咱們封裝一批組件,就借花獻佛拿出來分享一下吧!以前也沒寫過湊合看吧
哈哈。。。

首先來看下這個組件的樣子, 它長這樣:
圖片描述react

主要功能包括:jquery

  1. 組件內部展現內容隨意編輯,以便組件更好的複用(timeLineHTML);
  2. 點擊左右箭頭可使時間軸左右滑動,每次移動量以單元格數量爲準(moveCount),不能點擊時鼠標指針給與相應的提示,鼠標按住時間軸中間任意位置拖動可實現相同效果(mouseSliding);
  3. 組件能夠自動適應父元素寬高而且屏幕寬度變化時作到響應(height);

接下來看下代碼和思路吧!
首先觀察一下組件的樣式左右兩個按鈕且寬度固定,響應式改變的主要是中間部分的寬度,
antd組價選擇使用Layout佈局來寫,代碼以下:
圖片描述瀏覽器

佈局定下來了估計剩下的都會寫我就不囉嗦了,須要注意的是咱們在componentDidMount生命週期中記錄下頁面初次更新時,每個單元格的位置做爲初始位置,
圖片描述
下面主要說下鼠標觸發的滾動是怎麼是實現的;
廢話很少說直接上代碼:antd

// 時間軸滾動事件
  timeLineClick = (direction) => {
    const {moveCount} = this.props;
    let self = this;

    // 移動過程當中禁止點擊
    if (!this.allowClick) return;

    // 因使用scrollLeft 未達到最大值時scrollLeft已經達到最大 臨界判斷
    let lastPositionLeft = $('.diagnosis_box').eq(this.timeLineHTMLArr.length - 1).position().left;
    let lastWidth = $('.diagnosis_box').eq(this.timeLineHTMLArr.length - 1).width();
    let timeLinBoxWidth = $('#time_line_box').width() + _padding;

    if (direction === 'left' && $('.diagnosis_box').eq(0).position().left >= _padding) {
      return;
    }
    else if (direction === 'right') {
      if (Math.floor(lastPositionLeft + lastWidth) <= timeLinBoxWidth) {
        return
      }
    }

    this.allowClick = false;
    // 因拖拽屏幕會致使scrollLeft 有所變更 所這裏從新定位下 this.timeLineCounter
    let currentPosition = $('#time_line_box').scrollLeft() + _padding;
    if (this.isResize || (direction === 'left' && Math.floor(lastPositionLeft + lastWidth) <= timeLinBoxWidth)) {
      this.timeLineHTMLArr.some((item, index) => {
        if (index < this.timeLineHTMLArr.length - 1
          && item <= currentPosition
          && currentPosition <= this.timeLineHTMLArr[index + 1]
        ) {
          this.timeLineCounter = this.isResize ? index + 1 : index
        }
      });
    }

    let count = 0;
    // 根據操做移動timeline
    if (direction === 'left') {
      if (this.timeLineCounter >= 0) {

        this.timeLineCounter = this.timeLineCounter - moveCount >= 0
          ? this.timeLineCounter - moveCount
          : 0;
        let leftCount = Math.floor((currentPosition - self.timeLineHTMLArr[self.timeLineCounter]) / 5);
        let displacementTime = 200 / leftCount;

        function moveLeft(count) {
          setTimeout(() => {
            self.isResize = false;
            let counts = count + 1;
            let _left = currentPosition - _padding - counts * 5;
            $('#time_line_box').scrollLeft(_left);
            if (leftCount > count) {
              moveLeft(counts);
            }
            else {
              self.allowClick = true;

              let newLastPositionLeft = $('.diagnosis_box').eq(self.timeLineHTMLArr.length - 1).position().left;
              self.setState({
                left: self.timeLineCounter === 0 ? false : true,
                right: Math.floor(newLastPositionLeft + lastWidth) <= timeLinBoxWidth ? false : true
              });
            }
          }, displacementTime)
        }

        moveLeft(count);
      }
    }
    else if (direction === 'right') {
      if (this.timeLineCounter <= this.timeLineHTMLArr.length - 1) {

        this.timeLineCounter = this.timeLineCounter + moveCount < this.timeLineHTMLArr.length - 1
          ? this.timeLineCounter + moveCount
          : this.timeLineHTMLArr.length - 1;

        let leftCount = Math.floor((self.timeLineHTMLArr[self.timeLineCounter] - currentPosition) / 5);
        let displacementTime = 200 / leftCount;

        function moveRight(count) {
          setTimeout(() => {
            self.isResize = false;
            let counts = count + 1;
            let _left = currentPosition - _padding + counts * 5;
            $('#time_line_box').scrollLeft(_left);

            if (leftCount > count) {
              moveRight(counts);
            }
            else {
              self.allowClick = true;

              let newLastPositionLeft = $('.diagnosis_box').eq(self.timeLineHTMLArr.length - 1).position().left;
              self.setState({
                left: self.timeLineCounter === 0 ? false : true,
                right: Math.floor(newLastPositionLeft + lastWidth) <= timeLinBoxWidth ? false : true
              });
            }
          }, displacementTime)
        }

        moveRight(count);
      }
    }
  };

這裏的邏輯主要是鼠標點擊觸發的過程,須要注意的幾點:less

  1. 第一位移須要時間因此咱們須要在惟一過程當中,作一個節流,在位移結束前,不接受點擊事件;
  2. 因爲每個位移單位的距離不一樣,就會出現單元格長的位移時間也會過長,視覺效果也不是很好,因此裏面咱們固定每次位移時間爲200,用setTimeout遞歸調用;
  3. 在封裝組件的時候遇到一個比較尷尬的問題,第一個單元格不在最左端的時候鼠標拖拽瀏覽器晃動改變大小,會使頁面發生改變,致使計算好的位置出現誤差(使用鼠標拖拽發生位移時也須要這樣處理),因此代碼中會出現這樣一部分處理:

圖片描述

目的是爲了修正偏移量;剩下的就很少說了直接看代碼吧!ide

import React, {PureComponent} from 'react';
import styles from './TimeLine.less';
import {Icon, Layout} from 'antd';
import $ from "jquery";
import PropTypes from 'prop-types';
/*
* 公共時間軸 組件 接受定製顯示模塊 此組件提供模擬 獲取TimeLineHTML函數
* 必須給定特性的 className = diagnosis_box; 每次點擊保證diagnosis_box到達最左側
* 本身定製部分的樣式在 父組件內定義
* _padding 爲time_line_box兩邊的padding 因歸入原生計算 因此統一賦值
* */
const _padding = 24;
const {Sider, Content} = Layout;

let eleDrag = false;

class TimeLine extends PureComponent {
  static propTypes = {
    timeLineHTML: PropTypes.array.isRequired, // 默認選項
    moveCount: PropTypes.number, // 每次點擊移動個數
    height: PropTypes.number,
    mouseSliding: PropTypes.bool // 是否支持鼠標滑動事件
  };

  static defaultProps = {
    moveCount: 3,
    height: 65,
    mouseSliding: true
  };

  constructor(props) {
    super(props);
    this.state = {
      left: false,
      right: true
    };
    this.timeLineHTMLArr = [];
    this.timeLineCounter = 0;
    this.allowClick = true;
    this.isResize = false;
    this.initClientX = null;
  }

  componentDidMount() {
    let self = this;
    // 首次進入加載全部模塊的positionleft 做爲移動距離斷定
    if (!this.timeLineHTMLArr.length) {
      $('.diagnosis_box').each(function () {
        self.timeLineHTMLArr.push($(this).position().left);
      });
    }
    window.onresize = () => {
      let target = this;
      if (target.resizeFlag) {
        clearTimeout(target.resizeFlag);
      }
      target.resizeFlag = setTimeout(function () {
        self.isResize = true;
        target.resizeFlag = null;
      }, 100);
    };
  }

  // 時間軸滾動事件
  timeLineClick = (direction) => {
    const {moveCount} = this.props;
    let self = this;

    // 移動過程當中禁止點擊
    if (!this.allowClick) return;

    // 因使用scrollLeft 未達到最大值時scrollLeft已經達到最大 臨界判斷
    let lastPositionLeft = $('.diagnosis_box').eq(this.timeLineHTMLArr.length - 1).position().left;
    let lastWidth = $('.diagnosis_box').eq(this.timeLineHTMLArr.length - 1).width();
    let timeLinBoxWidth = $('#time_line_box').width() + _padding;

    if (direction === 'left' && $('.diagnosis_box').eq(0).position().left >= _padding) {
      return;
    }
    else if (direction === 'right') {
      if (Math.floor(lastPositionLeft + lastWidth) <= timeLinBoxWidth) {
        return
      }
    }

    this.allowClick = false;
    // 因拖拽屏幕會致使scrollLeft 有所變更 所這裏從新定位下 this.timeLineCounter
    let currentPosition = $('#time_line_box').scrollLeft() + _padding;
    if (this.isResize || (direction === 'left'
        && Math.floor(lastPositionLeft + lastWidth) <= timeLinBoxWidth)) {
      this.timeLineHTMLArr.some((item, index) => {
        if (index < this.timeLineHTMLArr.length - 1
          && item <= currentPosition
          && currentPosition <= this.timeLineHTMLArr[index + 1]
        ) {
          this.timeLineCounter = this.isResize ? index + 1 : index
        }
      });
    }

    let count = 0;
    // 根據操做移動timeline
    if (direction === 'left') {
      if (this.timeLineCounter >= 0) {

        this.timeLineCounter = this.timeLineCounter - moveCount >= 0
          ? this.timeLineCounter - moveCount
          : 0;
        let leftCount = Math.floor((currentPosition - self.timeLineHTMLArr[self.timeLineCounter]) / 5);
        let displacementTime = 200 / leftCount;

        function moveLeft(count) {
          setTimeout(() => {
            self.isResize = false;
            let counts = count + 1;
            let _left = currentPosition - _padding - counts * 5;
            $('#time_line_box').scrollLeft(_left);
            if (leftCount > count) {
              moveLeft(counts);
            }
            else {
              self.allowClick = true;

              let newLastPositionLeft = $('.diagnosis_box').eq(self.timeLineHTMLArr.length - 1).position().left;
              self.setState({
                left: self.timeLineCounter === 0 ? false : true,
                right: Math.floor(newLastPositionLeft + lastWidth) <= timeLinBoxWidth ? false : true
              });
            }
          }, displacementTime)
        }

        moveLeft(count);
      }
    }
    else if (direction === 'right') {
      if (this.timeLineCounter <= this.timeLineHTMLArr.length - 1) {

        this.timeLineCounter = this.timeLineCounter + moveCount < this.timeLineHTMLArr.length - 1
          ? this.timeLineCounter + moveCount
          : this.timeLineHTMLArr.length - 1;

        let leftCount = Math.floor((self.timeLineHTMLArr[self.timeLineCounter] - currentPosition) / 5);
        let displacementTime = 200 / leftCount;

        function moveRight(count) {
          setTimeout(() => {
            self.isResize = false;
            let counts = count + 1;
            let _left = currentPosition - _padding + counts * 5;
            $('#time_line_box').scrollLeft(_left);

            if (leftCount > count) {
              moveRight(counts);
            }
            else {
              self.allowClick = true;

              let newLastPositionLeft = $('.diagnosis_box').eq(self.timeLineHTMLArr.length - 1).position().left;
              self.setState({
                left: self.timeLineCounter === 0 ? false : true,
                right: Math.floor(newLastPositionLeft + lastWidth) <= timeLinBoxWidth ? false : true
              });
            }
          }, displacementTime)
        }

        moveRight(count);
      }
    }
  };

  // 拖拽鼠標使用時間軸滑動
  onMouseDown = (e) => {
    e.stopPropagation();
    e.preventDefault();
    eleDrag = true;
    this.initClientX = e.clientX;
  };

  onMouseMoveCapture = (e) => {
    e.stopPropagation();
    e.preventDefault();
    if (!this.allowClick) return;
    if (eleDrag) {
      let currentPosition = $('#time_line_box').scrollLeft();

      if (this.initClientX > e.clientX) { // 向右拖動鼠標
        let lastPositionLeft = $('.diagnosis_box').eq(this.timeLineHTMLArr.length - 1).position().left;
        let lastWidth = $('.diagnosis_box').eq(this.timeLineHTMLArr.length - 1).width();
        let timeLinBoxWidth = $('#time_line_box').width() + _padding;
        if (Math.floor(lastPositionLeft + lastWidth) <= timeLinBoxWidth) {
          return
        }
        let displacement = this.initClientX - e.clientX;
        $('#time_line_box').scrollLeft(currentPosition + displacement);
      }
      else if (this.initClientX < e.clientX) {// 向左拖動鼠標
        if ($('.diagnosis_box').eq(0).position().left >= _padding) {
          return;
        }
        let displacement = currentPosition - (e.clientX - this.initClientX);
        $('#time_line_box').scrollLeft(displacement);
      }
      this.initClientX = e.clientX;
    }
  };

  noDragging = (e) => {
    e.stopPropagation();
    e.preventDefault();
    eleDrag = false;
    this.initClientX = null;
  };

  render() {
    const {
      props: {
        timeLineHTML,
        height,
        mouseSliding
      },
      state: {
        left,
        right,
      }
    } = this;
    return (
      <div
        className={styles.TimeLine}
        style={{
          height,
          lineHeight: `${height}px`
        }}>
        <Layout>
          <Sider
            width={20}
            style={{
              position: 'relative',
              background: 'rgba(0, 0, 0, .25)',
              textAlign: 'center',
              cursor: left ? 'pointer' : 'not-allowed'
            }}>
            <span
              style={{
                position: 'absolute',
                left: '-1px',
                width: '100%',
                height: '100%',
                textAlign: 'center',
                display: 'inline-block',
                cursor: left ? 'pointer' : 'not-allowed'
              }}
              onMouseEnter={() => {
                if ($('.diagnosis_box').eq(0).position().left >= _padding - 0.01) {
                  this.setState({left: false});
                }
                else {
                  this.setState({left: true});
                }
              }}
              onClick={() => {
                this.timeLineClick('left')
              }}
            />
            <Icon type="left"/>
          </Sider>
          <Content
            style={{
              height: '100%',
              overflow: 'hidden',
              position: 'relative'
            }}>
            {mouseSliding
              ? <div
                onMouseDown={this.onMouseDown}
                onMouseMoveCapture={this.onMouseMoveCapture}
                onMouseUp={this.noDragging}
                onMouseLeave={this.noDragging}
                id='time_line_box'
                className={styles.time_line_box}>
                {timeLineHTML}
              </div>
              : <div
                id='time_line_box'
                className={styles.time_line_box}>
                {timeLineHTML}
              </div>}
          </Content>
          <Sider
            width={20}
            style={{
              position: 'relative',
              background: 'rgba(0, 0, 0, .25)',
              textAlign: 'center',
              cursor: right ? 'pointer' : 'not-allowed'
            }}>
             <span
               style={{
                 position: 'absolute',
                 left: '-1px',
                 width: '100%',
                 height: '100%',
                 textAlign: 'center',
                 display: 'inline-block',
                 cursor: right ? 'pointer' : 'not-allowed'
               }}
               onMouseEnter={() => {
                 let lastPositionLeft = $('.diagnosis_box').eq(this.timeLineHTMLArr.length - 1).position().left;
                 let lastWidth = $('.diagnosis_box').eq(this.timeLineHTMLArr.length - 1).width();
                 let timeLinBoxWidth = $('#time_line_box').width() + _padding;
                 if (Math.floor(lastPositionLeft + lastWidth) <= timeLinBoxWidth) {
                   this.setState({right: false});
                 }
                 else {
                   this.setState({right: true});
                 }
               }}
               onClick={() => {
                 this.timeLineClick('right')
               }}
             />
            <Icon type="right"/>
          </Sider>
        </Layout>
      </div>
    )
  }
}

export default TimeLine;

下面是對應的less:函數

.TimeLine{
  height: 100%;
  :global{
    .ant-layout.ant-layout-has-sider{
      height: 100%;
    }
  }

  .time_line_box{
    position: relative;
    padding: 0 24px;
    height: 100%;
    overflow: hidden;
    white-space: nowrap;
  }
}

下面是技術總結,主要說下這個組件須要注意的地方:佈局

  1. 鼠標點擊事件須要作判斷,在惟一過程當中應禁止下一次點擊事件;
  2. 其次鼠標拖拽之後,再次點擊移動或者屏幕寬度發生變化後在次點擊時,須要校訂一下偏移位置;
  3. 注意鼠標不可點擊時的臨界判斷;
相關文章
相關標籤/搜索