關于react 的拖曳組件

關于React的拖曳組件

1.使用的時候

 import buildDraggableArea from '../Drag/DraggableAreaBuilder';
const DraggableArea = buildDraggableArea();

2.然后用DraggableArea 組件包裹你 的元素

<DraggableArea

    initialTags={this.state.leftList}

    className="left"

    render={({tag}) => (

    <div>

        <Card.Grid style={gridStyle}>

            {tag.name}

        </Card.Grid>

    </div>

    )}

    onChange={(tags)=>this.onChange(tags)}

/>
你的組件就能拖動了

3.下面是源碼

import Reactfrom 'react';

import ReactDOMfrom "react-dom";

import { fromJS, List, is} from 'immutable';

import stylesfrom './style.less';

const isMobile = (typeof window.orientation!== "undefined") || (navigator.userAgent.indexOf('IEMobile') !== -1);

export default function buildDraggableArea({isInAnotherArea = () => {}, passAddFunc = () => {}} = {}) {

  return class DraggableArea extends React.Component {

    constructor() {

      super();

      this.state = {

        tags: List([]),

      }

      this.draggableTagEles = {};

      this.tagEles = {};

      this.positions = [];

      this.rect = {};

      this.dragStart = {};

      this.tagChanged = false;

}

    componentDidMount() {

      if (this.props.initialTags) {

        this.setTags(List(this.props.initialTags));

      } else {

        this.setTags(List(this.props.tags));

}

      passAddFunc(this.container, this.addTag.bind(this));

      this.props.getAddTagFunc&& this.props.getAddTagFunc(this.addTag.bind(this));

}

    componentWillReceiveProps({tags}) {

      if (!tags) return;

      console.log()

      if (tags.length !== this.props.tags.length|| tags.some((tag, i) => tag.id !== ((this.state.tags&&this.state.tags.size>0)&&this.state.tags.get(i).id))) {

        this.setTags(List(tags));

}

}

    componentDidUpdate(prevProps, {tags}) {

      this.tagChanged = this.tagChanged || this.state.tags.some((tag, i) => !tags.get(i) || tag.id !== tags.get(i).id);

}

    dragElement(elmnt, id, parent) {

      const isList = this.props.isList;

      let prevX = 0, prevY = 0;

      let rect = {};

      let index;

      this.positions.forEach((p, i) => {

        if (p.id === id) index = i;

});

      const dragStart = (e) => {

        // e.preventDefault();

        this.tagChanged = false;

        if (window.dragMouseDown) return;

        window.dragMouseDown = true;

        rect = this.container.getBoundingClientRect();

        e = e || window.event;

        prevX = e.clientX || e.touches[0].clientX;

        prevY = e.clientY || e.touches[0].clientY;

        elmnt.style.zIndex = 2;

        window.parentDragTag = elmnt.parentElement;

        while (window.parentDragTag && !window.parentDragTag.classList.contains('DraggableTags-tag-drag')) {

          window.parentDragTag = window.parentDragTag.parentElement;

}

        if (window.parentDragTag) window.parentDragTag.style.zIndex = 2;

        document.addEventListener("mouseup", closeDragElement, false);

        document.addEventListener("mousemove", elementDrag, false);

        elmnt.addEventListener("touchend", closeDragElement, false);

        elmnt.addEventListener("touchcancel", closeDragElement, false);

        elmnt.addEventListener("touchmove", elementDrag, false);

        this.positions.forEach((p, i) => {

          if (p.id === id) index = i;

});

}

      const elementDrag = (e) => {

        if (isMobile) this.container.style.overflowY = 'visible';

        // Prevent scrolling on mobile devices

        e.type=== 'touchmove' &&  e.preventDefault();

        // Figure out the new position of tag

        e = e || window.event;

        let clientX = e.clientX || e.touches[0].clientX;

        let clientY = e.clientY || e.touches[0].clientY;

        let movedX = clientX - prevX;

        let movedY = clientY - prevY;

        prevX = clientX;

        prevY = clientY;

        let t = elmnt.offsetTop + movedY;

        let l = elmnt.offsetLeft+ movedX;

        elmnt.style.top = t + "px";

        elmnt.style.left = l + "px";

        let baseCenterTop= parent.offsetTop + elmnt.offsetHeight/ 2;

        let baseCenterLeft = parent.offsetLeft+ elmnt.offsetWidth/ 2;

        // The center position of the tag

        let ctop = baseCenterTop + t;

        let cleft = baseCenterLeft + l;

        let i; // safari 10 bug

// Check if the tag could be put into a new position

        for (i = 0; i < this.positions.length- 1; i++) {

          // Do not check its left-side space and right-side space

          if ((index !== i || (index === this.positions.length- 2 && i === this.positions.length- 2)) && !(index - 1 === i && i !== 0)) {

            const p1 = this.positions[i];

            const p2 = this.positions[i+1];

            let isHead = false;

            let isTail = false;

            let between2Tags = false;

            let endOfLine = false;

            let startOfLine = false;

            if (!isList) {

              // Is not "list view"

              if (

                // Head of tag list

                i === 0 &&

                ctop > p1.top &&

                ctop < p1.bottom &&

                cleft < p1.left + 8

              ) isHead = true;

              if (

                // Tail of tag list

                i === this.positions.length- 2 && ((

                ctop > p2.top &&

                cleft > p2.left - 8) || ctop > p2.bottom)

              ) isTail = true;

              if (

                // Between two tags

                ctop > p1.top &&

                ctop < p1.bottom &&

                cleft > p1.right - 8 &&

                cleft < p2.left + 8

              ) between2Tags = true;

              if (

                // Start of line

                ctop > p2.top &&

                ctop < p2.bottom &&

                cleft < p2.left + 8 &&

                p1.top < p2.top

              ) startOfLine = true;

              if (

                // End of line

                ctop > p1.top &&

                ctop < p1.bottom &&

                cleft > p1.right - 8 &&

                p1.top < p2.top

              ) endOfLine = true;

            } else {

              // Is "list view"

              if (

                // Between two tags

                ctop < p1.bottom + 6 &&

                ctop > p2.top - 6

              ) between2Tags = true;

              if (

                // Head of tag list

                i === 0 &&

                ctop < p1.top + 4

              ) isHead = true;

              if (

                // Tail of tag list

                i === this.positions.length- 2 &&

                ctop > p2.bottom - 4

              ) isTail = true;

}

            if (

              (!isList && (isHead || isTail || between2Tags || startOfLine || endOfLine))

              ||

              (isList && (isHead || isTail || between2Tags))

) {

              let cur = this.state.tags.get(index);

              let tags = this.state.tags.splice(index, 1);

              if ((index < i || isHead) && !isTail) {

                tags = tags.splice(i, 0, cur);

                index = i;

              } else {

                tags = tags.splice(i+1, 0, cur);

                index = i + 1;

}

              this.positions = [];

              const prevBaseTop = this.tagEles[cur.id].offsetTop;

              const prevBaseLeft = this.tagEles[cur.id].offsetLeft;

              this.setState({tags}, () => {

                let curBaseTop;

                let curBaseLeft;

                tags.forEach((t, i) => {

                  const tag = this.tagEles[t.id];

                  if (i === index) {

                    curBaseLeft = tag.offsetLeft;

                    curBaseTop= tag.offsetTop;

}

                  this.positions.push({

                    id: t.id,

                    top: tag.offsetTop,

                    left: tag.offsetLeft,

                    bottom: tag.offsetTop + tag.offsetHeight,

                    right: tag.offsetLeft+ tag.offsetWidth,

                  });

});

                // 根據不同情況計算移動后的坐標

                if (curBaseLeft > prevBaseLeft) {

                  elmnt.style.left = `${l - (curBaseLeft - prevBaseLeft)}px`;

                } else {

                  elmnt.style.left = `${l + (prevBaseLeft - curBaseLeft)}px`;

}

                if (prevBaseTop > curBaseTop) {

                  elmnt.style.top = `${t + (prevBaseTop - curBaseTop)}px`;

                } else {

                  elmnt.style.top = `${t - (curBaseTop - prevBaseTop)}px`;

}

});

              break;

}

}

}

}

      const closeDragElement = (e) => {

        if (isMobile) this.container.style.overflowY = 'auto';

        window.dragMouseDown = false;

        document.removeEventListener("mouseup", closeDragElement, false);

        document.removeEventListener("mousemove", elementDrag, false);

        elmnt.removeEventListener("touchend", closeDragElement, false);

        elmnt.removeEventListener("touchcancel", closeDragElement, false);

        elmnt.removeEventListener("touchmove", elementDrag, false);

        if (window.parentDragTag) window.parentDragTag.style.zIndex = 1;

        let eRect = elmnt.getBoundingClientRect();

        let x = eRect.left+ eRect.width/ 2;

        let y = eRect.top+ eRect.height/ 2;

        if (x < rect.left || x > rect.right || y < rect.top || y > rect.bottom) {

          if (isInAnotherArea(elmnt.getBoundingClientRect(), this.state.tags.get(index))) {

            this.positions.splice(index, 1);

            this.setState({tags: this.state.tags.splice(index, 1)}, () => {

              this.props.onChange && this.props.onChange(this.state.tags.toJS());

});

            return;

}

}

        elmnt.style.top = 0;

        elmnt.style.left = 0;

        elmnt.style.zIndex = 1;

        if (this.tagChanged  && this.props.onChange) {

          this.tagChanged  = false;

          this.props.onChange(this.state.tags.toJS());

}

}

      elmnt.addEventListener("mousedown", dragStart, false);

      elmnt.addEventListener("touchstart", dragStart, false);

}

    setTags(tags, callback) {

      this.setState({tags}, () => {

        callback && callback();

        this.positions = [];

        this.state.tags.forEach((t, i) => {

          const draggableTag = this.draggableTagEles[t.id];

          const tag = this.tagEles[t.id];

          this.positions.push({

            id: t.id,

            top: tag.offsetTop,

            left: tag.offsetLeft,

            bottom: tag.offsetTop + tag.offsetHeight,

            right: tag.offsetLeft+ tag.offsetWidth,

          });

          this.dragElement(draggableTag, t.id, tag);

});

});

}

    addTag(tag) {

      this.setTags(this.state.tags.push(tag), () => {

        this.props.onChange && this.props.onChange(this.state.tags.toJS());

});

}

    buildDeleteTagFunc(tag) {

      return () => {

        const tags = this.state.tags.filter(t => tag.id !== t.id);

        this.setTags(tags, () => {

          this.props.onChange && this.props.onChange(this.state.tags.toJS());

});

}

}

    render() {

      let {render, build, style, className, isList, tagMargin = '5px', tagStyle} = this.props;

      if (!render) render = build;

      const tags = this.state.tags.toJS().map((tag) => (

        <div

          key={tag.id}

          className="DraggableTags-tag"

          ref={(target) => {

            this.tagEles[tag.id] = target;

}}

          style={isList ? {display: 'block', ...tagStyle} : tagStyle}

        >

          <div

            className="DraggableTags-tag-drag"

            ref={(target) => this.draggableTagEles[tag.id] = target}

          >

            {render({tag, deleteThis: this.buildDeleteTagFunc(tag)})}

          </div>

          <div style={{opacity: 0, overflow: 'hidden'}}>

            {render({tag, deleteThis: this.buildDeleteTagFunc(tag)})}

          </div>

        </div>

      ))

      return (

        <div

          ref={r => this.container = r}

          className={`DraggableTags ${className || ''}`}

          style={isMobile ? { overflowY: 'auto', ...style} : style}

        >

        {

          // To prevent body scroll on mobile device when dragging tags

          isMobile ? (<div style={{height: '101%'}}>{tags}</div>) : tags

        }

        </div>

      );

}

}

}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末脉漏,一起剝皮案震驚了整個濱河市铝阐,隨后出現(xiàn)的幾起案子契沫,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異锨阿,居然都是意外死亡,警方通過查閱死者的電腦和手機记罚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門墅诡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人桐智,你說我怎么就攤上這事末早。” “怎么了酵使?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵荐吉,是天一觀的道長焙糟。 經常有香客問我口渔,道長,這世上最難降的妖魔是什么穿撮? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任缺脉,我火速辦了婚禮痪欲,結果婚禮上,老公的妹妹穿的比我還像新娘攻礼。我一直安慰自己业踢,他們只是感情好,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布礁扮。 她就那樣靜靜地躺著知举,像睡著了一般。 火紅的嫁衣襯著肌膚如雪太伊。 梳的紋絲不亂的頭發(fā)上雇锡,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機與錄音僚焦,去河邊找鬼锰提。 笑死,一個胖子當著我的面吹牛芳悲,可吹牛的內容都是我干的立肘。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼名扛,長吁一口氣:“原來是場噩夢啊……” “哼谅年!你這毒婦竟也來了?” 一聲冷哼從身側響起肮韧,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤踢故,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后惹苗,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體殿较,經...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年桩蓉,在試婚紗的時候發(fā)現(xiàn)自己被綠了淋纲。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡院究,死狀恐怖洽瞬,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情业汰,我是刑警寧澤伙窃,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站样漆,受9級特大地震影響为障,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一鳍怨、第九天 我趴在偏房一處隱蔽的房頂上張望呻右。 院中可真熱鬧,春花似錦鞋喇、人聲如沸声滥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽落塑。三九已至,卻和暖如春罐韩,著一層夾襖步出監(jiān)牢的瞬間芜赌,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工伴逸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留缠沈,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓错蝴,卻偏偏與公主長得像洲愤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子顷锰,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355