一、起因&思路
一直想寫一個原生js拖拽效果,又加上近來學react學得比較嗨。所以就用react來實現這個拖拽效果。
首先,其實拖拽效果的思路是很簡單的。主要就是三個步驟:
1.onmousedown的時候,啟動可拖拽事件,記錄被拖拽元素的原始座標參數。
2.onmousemove的時候,即時記錄滑鼠移動的距離,結合被拖拽元素第一階段的座標參數,計算並設定新的座標值。
3.onmouseup的時候,關閉可拖拽事件,記錄新的座標值。
注意:這裡主要是通過絕對位置的top和left來確定元素的位置的,因此被拖拽元素的css一定要設定絕對位置。
二、協助工具輔助
協助工具輔助主要就是是開發過程變得高效,而且酷炫的。在這個demo中,要給大家推薦一個gulp+browser-sync的開發工具,gulp有很多功能,在這個demo中gulp的作用主要是可以設定Just-In-Time 編譯react中的jsx檔案,當然如果你寫css用的是sass,也可以設定Just-In-Time 編譯sass。用browser-sync這個呢,主要就是可以自動即時重新整理頁面,我們平時做頁面,看效果的時候,通常都是通過F5來重新整理瀏覽器,然後看到頁面的。但是用了這個外掛程式,你寫完代碼的時候,只要按下,ctrl+s儲存,新的效果就會自動在瀏覽器中重新整理,然後看得到了。
用法詳解:
安裝:
1.在node的環境下,安裝gulp,這裡就不詳說了,具體過程可參考我的博文《react.js入門必須知道的那些事》
2.安裝gulp-livereload,在命令列或者git bash ,輸入npm install --save-dev gulp-livereload
3.安裝gulp-watch,在命令列或者git bash ,輸入npm install --save-dev gulp-watch
4.安裝browser-sync,在命令列或者git bash ,輸入npm install --save-dev browser-sync
配置及解釋如圖:
三、定義組件構建頁面
備忘:這裡的代碼說明均在react相關模組安裝好的情況下,安裝過程見我的博文《react.js入門必須知道的那些事》.
效果圖:
組件拆分思路:
我當時覺得組件拆分得細一點好,所以我把input、button分別做成了一個組件:
var React=require('react'); var MyInput=React.createClass({ render:function(){ return ( <div className="form-group"> <label htmlFor={this.props.labelId} className="col-sm-2 control-label{this.props.labelTip</label> <div className="col-sm-10"> <input name={this.props.name} type={this.props.type} onChange={this.props.onChange} className="form-control" id={this.props.labelId} placeholder={this.props.placeholder}/> </div> </div> ); }}); module.exports=MyInput;
var React=require('react'); var Button=React.createClass({ render:function(){ return ( <button type={this.props.type} className="loginButton">{this.props.ButtonTip}</button> ); }})module.exports=Button;
由於input有很多都是需要指定的,這種情況下,如果像我這樣定義需要傳太多參數,而且其實登陸的input大多都是固定且沒必要複用的,所以這樣其實不大好。這裡的input直接寫比較好。
寫好之後的父組件:
render:function(){ return ( <form className="form-horizontal" id="form" ref="dragBox" onSubmit={this.submitHandler} onMouseMove={this.move} onMouseUp={this.endDrag}> <DragArea callbackParent={this.onChildChanged} /> <div id="form-wrap"> <MyInput name="username" labelId={"userId"} labelTip={"使用者名稱"} type={"text"} placeholder={"請輸入使用者名稱"} value={this.state.username} onChange={this.handleChange}/> <MyInput name="password" labelId={"pw"} labelTip={"密碼"} type={"password"} placeholder={"請輸入密碼"} value={this.state.password} onChange={this.handleChange}/> <div className="form-group"> <div className="col-sm-offset-2 col-sm-10"> <div className="checkbox"> <label> <input name="checked" type="checkbox" checked={this.state.checked} onChange={this.handleChange} /> 記住我 </label> </div> </div> </div> <MyButton type={"submit"} ButtonTip={"登陸"}/> </div> </form> );
備忘:因為demo中需要擷取真實的dom節點,所以定義了ref。
再加上css樣式,頁面就完成啦!最後,重點來啦!!!
四、父子組件間通訊實現拖拽
說明:由於我要實現的效果是,滑鼠按住子組件DragArea的時候,拖動的是整個form,所以啟動拖拽的是DragArea,而響應的是form。所以,一開始必須把父組件的一些狀態屬性傳給子組件,然後滑鼠在DragArea按下的的時候,必須通過子組件DragArea找到父組件的原始座標參數,然後更新父組件裡面的狀態屬性,並且告訴父組件可以進行拖拽了。父組件給子組件傳參就是直接傳遞的。而子組件給父組件傳參需要通過事件。所以在父組件中定義這麼一個函數:
onChildChanged:function(newState){ //因為參數過多,所以把參數放到對象裡面,通過對象來傳 this.setState(newState);},
而子組件需要綁定這個函數,如上面的代碼:callbackParent={this.onChildChanged}
在子組件中,響應的函數為:
startDrag:function(e){ var dragBox=document.getElementById('form'); var newState={}; var event=e||window.event; event.preventDefault(); var computedStyle=document.defaultView.getComputedStyle(dragBox,null); newState.left=computedStyle.left; newState.top=computedStyle.top; newState.currentX=event.clientX; newState.currentY=event.clientY; newState.flag=true; <span style="color: #0000ff;"> this.props.callbackParent(newState);</span>}
這樣,在子組件中就啟動了拖拽開關,並且已經更新了from的相關參數,from的兩外兩個事件,move和endDrag分別為:
move:function(event){ var e = event ? event : window.event; //相容IE的寫法 if (this.state.flag) { var nowX = e.clientX, nowY = e.clientY; var disX = nowX - this.state.currentX, disY = nowY - this.state.currentY; ReactDOM.findDOMNode(this.refs.dragBox).style.left = parseInt(this.state.left) + disX + "px"; ReactDOM.findDOMNode(this.refs.dragBox).style.top = parseInt(this.state.top) + disY + "px"; }},endDrag:function(){ var computedStyle=document.defaultView.getComputedStyle(ReactDOM.findDOMNode(this.refs.dragBox),null); this.setState({ left:computedStyle.left, top:computedStyle.top, flag:false });}
至此,拖拽實現!
五、反思回顧
1.理論上來說,拖拽效果可以在任意元素中實現,拖拽的思路都是一致的,所以理論上來說,拖拽各個過程的函數可以抽離出來,做成一個Mixin,然後可以反覆調用。我一開始的思路就是這樣,但是在傳參、響應、繫結元素上面總是出錯。尋找了一下資料,沒找到react與拖拽的簡單寫法資料,只有一些react的專用外掛程式,而且是用ES6的寫法,由於現在的水平還沒能看懂。所以暫時放棄了這種寫法。希望有相關想法的大神們和我交流一下。
2.文中子組件擷取from的參數時,用了var dragBox=document.getElementById('form');去找dom,這樣好像違反了react的一些理念。但是我還不是很熟悉該怎麼從子組件擷取父組件的dom。我試過在父組件定義refs=this.refs.dragBox。然後傳給子組件,但是不知道為什麼瀏覽器一直報錯說這個不是dom節點。求大神指教。
3.拖拽事件的一般寫法,是在document上面定義mousemove和mouseup事件,但是這兩個事件都關聯到from的參數,這樣的話,如果我在react中定義在document,就跟蹤不了相關參數。所以我就定義在了from上面。是不是有更好的方法呢?求分享!
4.革命尚未成功,同志仍需努力!
本demo已上傳至:https://github.com/LuckyWinty/dragDemo
以上就是本文的全部內容,希望對大家的學習有所協助。