一、起因&思路
一直想寫一個原生js拖曳效果,又加上近來學react學得比較嗨。所以就用react來實現這個拖曳效果。
首先,其實拖曳效果的思路是很簡單的。主要就是三個步驟:
1.onmousedown的時候,啟動可拖曳事件,記錄被拖曳元素的原始座標參數。
2.onmousemove的時候,即時記錄滑鼠移動的距離,結合被拖曳元素第一階段的座標參數,計算並設定新的座標值。
3.onmouseup的時候,關閉可拖曳事件,記錄新的座標值。
注意:這裡主要是透過絕對定位的top和left來確定元素的位置的,因此被拖曳元素的css一定要設定絕對定位。
二、輔助工具
輔助工具主要就是是開發過程變得高效,而且酷炫的。在這個demo中,要給大家推薦一個gulp+browser-sync的開發工具,gulp有很多功能,在這個demo中gulp的作用主要是可以設定實時編譯react中的jsx文件,當然如果你寫css用的是sass,也可以設定即時編譯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 }); }
At this point, drag and drop is implemented!
5. Reflection
1. Theoretically, the drag effect can be realized in any element, and the idea of drag is the same, so theoretically, the functions of each drag process can be extracted and made into a Mixin, and then Can be called repeatedly. This was my initial idea, but I always made mistakes when passing parameters, responding, and binding elements. After searching for information, I couldn't find information on the simple writing methods of react and drag and drop. There were only some special plug-ins for react, and they were written in ES6. I can't understand them at my current level. So I gave up this way of writing for the time being. I hope that experts who have relevant ideas can share with me.
2. When the subcomponent in the article obtains the parameters from, it uses var dragBox=document.getElementById('form'); to find the dom, which seems to violate some concepts of react. But I'm not very familiar with how to get the dom of the parent component from the child component. I've tried defining refs=this.refs.dragBox in the parent component. Then it is passed to the sub-component, but I don’t know why the browser keeps reporting an error saying that this is not a dom node. Ask God for guidance.
3. The general way to write drag and drop events is to define mousemove and mouseup events on the document, but these two events are associated with the parameters of from. In this case, if I define them in the document in react, I cannot track the related events. parameter. So I defined it on from. Is there a better way? Please share!
4. The revolution has not yet succeeded, comrades still need to work hard!
This demo has been uploaded to: https://github.com/LuckyWinty/dragDemo
The above is the entire content of this article, I hope it will be helpful to everyone’s study.