基于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 相关文章推荐
服务端 VBScript 与 JScript 几个相同特性的写法 By shawl.qiu
Mar 06 Javascript
Javascript操作select方法大全[新增、修改、删除、选中、清空、判断存在等]
Sep 26 Javascript
从JavaScript 到 JQuery (1)学习小结
Feb 12 Javascript
script标签属性type与language使用选择
Dec 02 Javascript
jQuery满屏焦点图左右滚动特效代码分享
Sep 07 Javascript
Bootstrap中文本框的宽度变窄并且加入一副验证码图片的实现方法
Jun 23 Javascript
JS提示:Uncaught SyntaxError: Unexpected token ILLEGAL错误的解决方法
Aug 19 Javascript
微信小程序 表单Form实例详解(附源码)
Dec 22 Javascript
Angularjs为ng-click事件传递参数
Jun 15 Javascript
JavaScript实现离开页面前提示功能【附jQuery实现方法】
Sep 26 jQuery
在layui.use 中自定义 function 的正确方法
Sep 16 Javascript
element-ui tooltip修改背景颜色和箭头颜色的实现
Dec 16 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截断标题且兼容utf8和gb2312编码
2013/09/22 PHP
用PHP将Unicode 转化为UTF-8的实现方法(推荐)
2017/02/08 PHP
JavaScript类属性的访问方式详解
2014/02/11 Javascript
js正则表达式验证邮件地址
2015/11/12 Javascript
js拖拽的原型声明和用法总结
2016/04/04 Javascript
JavaScript通过HTML的class来获取HTML元素的方法总结
2016/05/24 Javascript
Jquery和BigFileUpload实现大文件上传及进度条显示
2016/06/27 Javascript
利用n 升级工具升级Node.js版本及在mac环境下的坑
2017/02/15 Javascript
JavaScript仿微信打飞机游戏
2020/07/05 Javascript
10分钟上手vue-cli 3.0 入门介绍
2018/04/04 Javascript
js canvas画布实现高斯模糊效果
2018/11/27 Javascript
mpvue全局引入sass文件的方法步骤
2019/03/06 Javascript
微信小程序 组件的外部样式externalClasses使用详解
2019/09/06 Javascript
微信小程序网络请求实现过程解析
2019/11/06 Javascript
Vue 禁用浏览器的前进后退操作
2020/09/04 Javascript
vue缓存之keep-alive的理解和应用详解
2020/11/02 Javascript
前端 javascript 实现文件下载的示例
2020/11/24 Javascript
Python群发邮件实例代码
2014/01/03 Python
Python中pygame安装方法图文详解
2015/11/11 Python
Python中函数参数调用方式分析
2018/08/09 Python
解决项目pycharm能运行,在终端却无法运行的问题
2019/01/19 Python
pytorch中tensor张量数据类型的转化方式
2019/12/31 Python
tensorflow之并行读入数据详解
2020/02/05 Python
CSS3中的Transition过度与Animation动画属性使用要点
2016/05/20 HTML / CSS
Vans(范斯)德国官网:美国南加州的原创极限运动潮牌
2017/05/02 全球购物
商务日语毕业生自荐信范文
2013/11/14 职场文书
计算机专业大学生的自我评价
2013/11/14 职场文书
个人查摆剖析材料
2014/02/04 职场文书
遵纪守法演讲稿
2014/05/23 职场文书
十佳青年事迹材料
2014/08/21 职场文书
2014旅游局领导班子四风问题对照检查材料思想汇报
2014/09/19 职场文书
升学宴祝酒词
2015/08/11 职场文书
妇联2016年六一国际儿童节活动总结
2016/04/06 职场文书
预备党员入党思想汇报(范文)
2019/08/14 职场文书
python实现局部图像放大
2021/11/17 Python
golang生成vcf通讯录格式文件详情
2022/03/25 Golang