基于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 相关文章推荐
关于Javascript 的 prototype问题。
Jan 03 Javascript
jQuery常用操作方法及常用函数总结
Jun 19 Javascript
jQuery中 attr() 方法使用小结
May 03 Javascript
JavaScript验证Email(3种方法)
Sep 21 Javascript
JS利用cookie记忆当前位置的防刷新导航效果
Oct 15 Javascript
Bootstrap每天必学之轮播(Carousel)插件
Apr 25 Javascript
js与applet相互调用的方法
Jun 22 Javascript
使用JS轻松实现ionic调用键盘搜索功能(超实用)
Sep 06 Javascript
JSON的parse()方法介绍
Jan 31 Javascript
详解vue使用$http服务端收不到参数
Apr 19 Javascript
微信小程序引入Vant组件库过程解析
Aug 06 Javascript
如何在vue 中引入使用jquery
Nov 10 jQuery
基于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入门小知识
2008/03/24 PHP
一个PHP数组应该有多大的分析
2009/07/30 PHP
php 使用post,get的一种简洁方式
2010/04/25 PHP
header导出Excel应用示例
2014/01/24 PHP
PHP自带方法验证邮箱是否存在
2016/02/01 PHP
PHP简单实现模拟登陆功能示例
2017/09/15 PHP
Javascript 中介者模式实例
2009/12/16 Javascript
多种方法判断Javascript对象是否存在
2013/09/22 Javascript
Easyui form combobox省市区三级联动
2016/01/13 Javascript
超赞的jQuery图片滑块动画特效代码汇总
2016/01/25 Javascript
picLazyLoad 实现图片延时加载(包含背景图片)
2016/07/21 Javascript
基本DOM节点操作
2017/01/17 Javascript
JavaScript如何对图片进行黑白化
2018/04/10 Javascript
微信小程序表单验证form提交错误提示效果
2020/06/19 Javascript
JS实现简单移动端鼠标拖拽
2020/07/23 Javascript
vue自定义组件实现双向绑定
2021/01/13 Vue.js
python使用json序列化datetime类型实例解析
2018/02/11 Python
python一行sql太长折成多行并且有多个参数的方法
2018/07/19 Python
在win10和linux上分别安装Python虚拟环境的方法步骤
2019/05/09 Python
scrapy-redis源码分析之发送POST请求详解
2019/05/15 Python
python数据归一化及三种方法详解
2019/08/06 Python
在macOS上搭建python环境的实现方法
2019/08/13 Python
Matplotlib scatter绘制散点图的方法实现
2020/01/02 Python
详解css3 object-fit属性
2018/07/27 HTML / CSS
HTML5单页面手势滑屏切换原理分析
2017/07/10 HTML / CSS
HTML5高仿微信聊天、微信聊天表情|对话框|编辑器功能
2018/04/23 HTML / CSS
Under Armour西班牙官网:美国知名的高端功能性运动品牌
2018/12/12 全球购物
POP文化和音乐灵感的时尚:Hot Topic
2019/06/19 全球购物
《三袋麦子》教学反思
2014/03/02 职场文书
党员先进性教育整改措施
2014/09/18 职场文书
故宫导游词
2015/01/31 职场文书
《语言的突破》读后感3篇
2019/12/12 职场文书
Nginx使用X-Accel-Redirect实现静态文件下载的统计、鉴权、防盗链、限速等
2021/04/04 Servers
Python中threading库实现线程锁与释放锁
2021/05/17 Python
一文搞懂redux在react中的初步用法
2021/06/09 Javascript