基于React.js實(shí)現(xiàn)原生js拖拽效果引發(fā)的思考
一、起因&思路
一直想寫一個(gè)原生js拖拽效果,又加上近來(lái)學(xué)react學(xué)得比較嗨。所以就用react來(lái)實(shí)現(xiàn)這個(gè)拖拽效果。
首先,其實(shí)拖拽效果的思路是很簡(jiǎn)單的。主要就是三個(gè)步驟:
1.onmousedown的時(shí)候,啟動(dòng)可拖拽事件,記錄被拖拽元素的原始坐標(biāo)參數(shù)。
2.onmousemove的時(shí)候,實(shí)時(shí)記錄鼠標(biāo)移動(dòng)的距離,結(jié)合被拖拽元素第一階段的坐標(biāo)參數(shù),計(jì)算并設(shè)置新的坐標(biāo)值。
3.onmouseup的時(shí)候,關(guān)閉可拖拽事件,記錄新的坐標(biāo)值。
注意:這里主要是通過(guò)絕對(duì)定位的top和left來(lái)確定元素的位置的,因此被拖拽元素的css一定要設(shè)置絕對(duì)定位。
二、輔助工具
輔助工具主要就是是開(kāi)發(fā)過(guò)程變得高效,而且酷炫的。在這個(gè)demo中,要給大家推薦一個(gè)gulp+browser-sync的開(kāi)發(fā)工具,gulp有很多功能,在這個(gè)demo中g(shù)ulp的作用主要是可以設(shè)置實(shí)時(shí)編譯react中的jsx文件,當(dāng)然如果你寫css用的是sass,也可以設(shè)置實(shí)時(shí)編譯sass。用browser-sync這個(gè)呢,主要就是可以自動(dòng)實(shí)時(shí)刷新頁(yè)面,我們平時(shí)做頁(yè)面,看效果的時(shí)候,通常都是通過(guò)F5來(lái)刷新瀏覽器,然后看到頁(yè)面的。但是用了這個(gè)插件,你寫完代碼的時(shí)候,只要按下,ctrl+s保存,新的效果就會(huì)自動(dòng)在瀏覽器中刷新,然后看得到了。
用法詳解:
安裝:
1.在node的環(huán)境下,安裝gulp,這里就不詳說(shuō)了,具體過(guò)程可參考我的博文《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
配置及解釋如圖:

三、定義組件構(gòu)建頁(yè)面
備注:這里的代碼說(shuō)明均在react相關(guān)模塊安裝好的情況下,安裝過(guò)程見(jiàn)我的博文《react.js入門必須知道的那些事》.
效果圖:

組件拆分思路:
我當(dāng)時(shí)覺(jué)得組件拆分得細(xì)一點(diǎn)好,所以我把input、button分別做成了一個(gè)組件:
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有很多都是需要指定的,這種情況下,如果像我這樣定義需要傳太多參數(shù),而且其實(shí)登陸的input大多都是固定且沒(méi)必要復(fù)用的,所以這樣其實(shí)不大好。這里的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={"請(qǐng)輸入用戶名"} value={this.state.username} onChange={this.handleChange}/>
<MyInput name="password" labelId={"pw"} labelTip={"密碼"} type={"password"} placeholder={"請(qǐng)輸入密碼"} 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>
);
備注:因?yàn)閐emo中需要獲取真實(shí)的dom節(jié)點(diǎn),所以定義了ref。
再加上css樣式,頁(yè)面就完成啦!最后,重點(diǎn)來(lái)啦?。?!
四、父子組件間通信實(shí)現(xiàn)拖拽
說(shuō)明:由于我要實(shí)現(xiàn)的效果是,鼠標(biāo)按住子組件DragArea的時(shí)候,拖動(dòng)的是整個(gè)form,所以啟動(dòng)拖拽的是DragArea,而響應(yīng)的是form。所以,一開(kāi)始必須把父組件的一些狀態(tài)屬性傳給子組件,然后鼠標(biāo)在DragArea按下的的時(shí)候,必須通過(guò)子組件DragArea找到父組件的原始坐標(biāo)參數(shù),然后更新父組件里面的狀態(tài)屬性,并且告訴父組件可以進(jìn)行拖拽了。父組件給子組件傳參就是直接傳遞的。而子組件給父組件傳參需要通過(guò)事件。所以在父組件中定義這么一個(gè)函數(shù):
onChildChanged:function(newState){ //因?yàn)閰?shù)過(guò)多,所以把參數(shù)放到對(duì)象里面,通過(guò)對(duì)象來(lái)傳
this.setState(newState);
},
而子組件需要綁定這個(gè)函數(shù),如上面的代碼:callbackParent={this.onChildChanged}
在子組件中,響應(yīng)的函數(shù)為:
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>
}
這樣,在子組件中就啟動(dòng)了拖拽開(kāi)關(guān),并且已經(jīng)更新了from的相關(guān)參數(shù),from的兩外兩個(gè)事件,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
});
}
至此,拖拽實(shí)現(xiàn)!
五、反思回顧
1.理論上來(lái)說(shuō),拖拽效果可以在任意元素中實(shí)現(xiàn),拖拽的思路都是一致的,所以理論上來(lái)說(shuō),拖拽各個(gè)過(guò)程的函數(shù)可以抽離出來(lái),做成一個(gè)Mixin,然后可以反復(fù)調(diào)用。我一開(kāi)始的思路就是這樣,但是在傳參、響應(yīng)、綁定元素上面總是出錯(cuò)。查找了一下資料,沒(méi)找到react與拖拽的簡(jiǎn)單寫法資料,只有一些react的專用插件,而且是用ES6的寫法,由于現(xiàn)在的水平還沒(méi)能看懂。所以暫時(shí)放棄了這種寫法。希望有相關(guān)想法的大神們和我交流一下。
2.文中子組件獲取from的參數(shù)時(shí),用了var dragBox=document.getElementById('form');去找dom,這樣好像違反了react的一些理念。但是我還不是很熟悉該怎么從子組件獲取父組件的dom。我試過(guò)在父組件定義refs=this.refs.dragBox。然后傳給子組件,但是不知道為什么瀏覽器一直報(bào)錯(cuò)說(shuō)這個(gè)不是dom節(jié)點(diǎn)。求大神指教。
3.拖拽事件的一般寫法,是在document上面定義mousemove和mouseup事件,但是這兩個(gè)事件都關(guān)聯(lián)到from的參數(shù),這樣的話,如果我在react中定義在document,就跟蹤不了相關(guān)參數(shù)。所以我就定義在了from上面。是不是有更好的方法呢?求分享!
4.革命尚未成功,同志仍需努力!
本demo已上傳至:https://github.com/LuckyWinty/dragDemo
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助。
相關(guān)文章
React18使用Echarts和MUI實(shí)現(xiàn)一個(gè)交互性的溫度計(jì)
這篇文章我們將結(jié)合使用React 18、Echarts和MUI(Material-UI)庫(kù),展示如何實(shí)現(xiàn)一個(gè)交互性的溫度計(jì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-01-01
如何使用React構(gòu)建一個(gè)高效的視頻上傳組件
本文介紹了如何使用React構(gòu)建一個(gè)高效的視頻上傳組件,包括基礎(chǔ)概念、常見(jiàn)問(wèn)題與解決方案以及易錯(cuò)點(diǎn),通過(guò)實(shí)際代碼案例,展示了如何實(shí)現(xiàn)文件大小和格式驗(yàn)證、上傳進(jìn)度顯示等功能,并詳細(xì)解釋了跨域請(qǐng)求和并發(fā)上傳控制等技術(shù)細(xì)節(jié)2025-01-01
使用Jenkins部署React項(xiàng)目的方法步驟
這篇文章主要介紹了使用Jenkins部署React項(xiàng)目的方法步驟,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-03-03
React?18中的state概念與使用、注意問(wèn)題解決
state和props類似,都是一種存儲(chǔ)屬性的方式,但是不同點(diǎn)在于state只屬于當(dāng)前組件,其他組件無(wú)法訪問(wèn),這篇文章主要介紹了React?18中的state概念與使用、注意問(wèn)題,需要的朋友可以參考下2022-12-12
React Navigation 使用中遇到的問(wèn)題小結(jié)
本篇文章主要介紹了React Navigation 使用中遇到的問(wèn)題小結(jié),主要是安卓和iOS中相對(duì)不協(xié)調(diào)的地方,特此記錄,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-05-05
React中g(shù)etDefaultProps的使用小結(jié)
React中的getDefaultProps功能允許開(kāi)發(fā)者為類組件定義默認(rèn)屬性,提高組件的靈活性和容錯(cuò)性,本文介紹了getDefaultProps的作用、語(yǔ)法以及最佳實(shí)踐,并探討了其他替代方案,如函數(shù)組件中的默認(rèn)參數(shù)、高階組件和ContextAPI等,理解這些概念有助于提升代碼的可維護(hù)性和用戶體驗(yàn)2024-09-09
使用Axios在React中請(qǐng)求數(shù)據(jù)的方法詳解
這篇文章主要給大家介紹了初學(xué)React,如何規(guī)范的在react中請(qǐng)求數(shù)據(jù),主要介紹了使用axios進(jìn)行簡(jiǎn)單的數(shù)據(jù)獲取,加入狀態(tài)變量,優(yōu)化交互體驗(yàn),自定義hook進(jìn)行數(shù)據(jù)獲取和使用useReducer改造請(qǐng)求,本文主要適合于剛接觸React的初學(xué)者以及不知道如何規(guī)范的在React中獲取數(shù)據(jù)的人2023-09-09
簡(jiǎn)談創(chuàng)建React Component的幾種方式
這篇文章主要介紹了創(chuàng)建React Component的幾種方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,,需要的朋友可以參考下2019-06-06

