基于React.js实现原生js拖拽效果引发的思考


Posted in Javascript onMarch 30, 2016

一、起因&思路

一直想写一个原生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.js实现原生js拖拽效果引发的思考

三、定义组件构建页面

备注:这里的代码说明均在react相关模块安装好的情况下,安装过程见我的博文《react.js入门必须知道的那些事》.

效果图:

基于React.js实现原生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

以上就是本文的全部内容,希望对大家的学习有所帮助。

Javascript 相关文章推荐
JQuery从头学起第二讲
Jul 04 Javascript
javascript实现连续赋值
Aug 10 Javascript
纯javascript实现图片延时加载方法
Aug 21 Javascript
JavaScript实现的select点菜功能示例
Jan 16 Javascript
用file标签实现多图文件上传预览
Feb 14 Javascript
JavaScript中splice与slice的区别
May 09 Javascript
详解使用路由延迟加载 Angular 模块
Oct 12 Javascript
Vue源码学习之初始化模块init.js解析
Nov 02 Javascript
基于vue组件实现猜数字游戏
May 28 Javascript
微信小程序实现topBar底部选择栏效果
Jul 20 Javascript
VUE+elementui面包屑实现动态路由详解
Nov 04 Javascript
Node.js 中判断一个文件是否存在
Aug 24 Javascript
基于jQuery实现Ajax验证用户名是否存在实例
Mar 30 #Javascript
jQuery表格插件datatables用法汇总
Mar 29 #Javascript
基于javascript实现tab切换特效
Mar 29 #Javascript
基于javascript实现简单的抽奖系统
Apr 15 #Javascript
基于javascript实现九宫格大转盘效果
May 28 #Javascript
全屏js头像上传插件源码高清版
Mar 29 #Javascript
js闭包引起的事件注册问题介绍
Mar 29 #Javascript
You might like
PHP实现清除wordpress里恶意代码
2015/10/21 PHP
讲解WordPress中用于获取评论模板和搜索表单的PHP函数
2015/12/28 PHP
laravel csrf排除路由,禁止,关闭指定路由的例子
2019/10/21 PHP
jQuery Validation插件remote验证方式的Bug解决
2010/07/01 Javascript
使用jQuery模板来展现json数据的代码
2010/10/22 Javascript
返回对象在当前级别中是第几个元素的实现代码
2011/01/20 Javascript
一行代码告别document.getElementById
2012/06/01 Javascript
JS、CSS以及img对DOMContentLoaded事件的影响
2014/08/12 Javascript
jQuery中nextAll()方法用法实例
2015/01/07 Javascript
使用 JavaScript 进行函数式编程 (一) 翻译
2015/10/02 Javascript
Javascript动画效果(1)
2016/10/11 Javascript
jQuery删除当前节点元素
2016/12/07 Javascript
详解Angular之constructor和ngOnInit差异及适用场景
2017/06/22 Javascript
vue实现tab切换外加样式切换方法
2018/03/16 Javascript
JS实现关键词高亮显示正则匹配
2018/06/22 Javascript
微信小程序实现的自定义分享功能示例
2019/02/12 Javascript
详解JavaScript原生封装ajax请求和Jquery中的ajax请求
2019/02/14 jQuery
jquery分页优化操作实例分析
2019/08/23 jQuery
解决layui数据表格Date日期格式的回显Object的问题
2019/09/19 Javascript
Layui表格监听行单双击事件讲解
2019/11/14 Javascript
python 字符串格式化代码
2013/03/17 Python
Python中用Descriptor实现类级属性(Property)详解
2014/09/18 Python
Python使用matplotlib绘图无法显示中文问题的解决方法
2018/03/14 Python
python多行字符串拼接使用小括号的方法
2020/03/19 Python
简单谈谈python基本数据类型
2018/09/26 Python
Python开发的十个小贴士和技巧及长常犯错误
2018/09/27 Python
对Python的zip函数妙用,旋转矩阵详解
2018/12/13 Python
TecoBuy澳大利亚:在线电子和小工具商店
2020/06/25 全球购物
自荐信写法介绍
2014/01/25 职场文书
市场营销策划方案
2014/06/11 职场文书
书法兴趣小组活动总结
2014/07/07 职场文书
学生会干部自我鉴定2014
2014/09/18 职场文书
2015年南京大屠杀纪念日活动总结
2015/03/24 职场文书
2015年健康教育工作总结
2015/04/10 职场文书
计算机教师工作总结
2015/08/13 职场文书
《青山不老》教学反思
2016/02/22 职场文书