文档章节

React.js实现原生js拖拽效果及思考

LuckyWinty
 LuckyWinty
发布于 2016/03/29 17:53
字数 1873
阅读 1621
收藏 2

一、起因&思路

不知不觉,已经好几天没写博客了。。。近来除了研究React,还做了公司官网。。。

一直想写一个原生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分别做成了一个组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;
1
2
3
4
5
6
7
8
9
10
11
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直接写比较好。

写好之后的父组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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找到父组件的原始坐标参数,然后更新父组件里面的状态属性,并且告诉父组件可以进行拖拽了。父组件给子组件传参就是直接传递的。而子组件给父组件传参需要通过事件。所以在父组件中定义这么一个函数:

1
2
3
onChildChanged:function(newState){ //因为参数过多,所以把参数放到对象里面,通过对象来传
    this.setState(newState);
},

而子组件需要绑定这个函数,如上面的代码:callbackParent={this.onChildChanged}

在子组件中,响应的函数为:

1
2
3
4
5
6
7
8
9
10
11
12
13
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分别为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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

备注:由于本demo比较简单,理解代码应该没有什么问题,所以没有写代码说明,请见谅!

© 著作权归作者所有

LuckyWinty
粉丝 10
博文 24
码字总数 32476
作品 0
广州
前端工程师
私信 提问
HTML5原生拖放事件的学习与实践

前言 之前学习了HTML5的拖放事件,开发中也用到了拖拽组件。为了厘清整体的逻辑,专门做了一个小例子。 具体实现的效果也很简单:元素可以在容器中任意拖动,元素被移入容器的时候,还会有相...

godbmw
04/20
0
0
c#(wpf)中WebBrowser拖动来移动窗口,改变窗口位置

前言 wpf中的WebBrowser相比之前的winform阉割了不少东西,也增加了不少东西,但是msdn对wpf也没有较好的文档 WebBrowser可以说是一个.NET控件,相对于WPF中的控件,不在同一线程,它们不可重...

白超华
01/10
0
0
《JavaScript实用效果整理》系列分享专栏

整理一些使用的JavaScript效果,在Web开发中遇到的比较好的动态效果,都收藏在这里,对以后的网站开发增加不少的色彩 《JavaScript实用效果整理》已整理成PDF文档,点击可直接下载至本地查阅...

开元中国2015
2018/10/29
62
0
jq效果在清理缓存之后就生效了为什么?版本问题??

如图,我需要这样才能让js生效。但是我的js不是ajax,只是单纯的一个拖拽功能。 向左转| 向右转 我代码是这样的, 能详细点告诉我应该怎么做吗?或者本身代码有问题? jquery.easyui的版本是...

空晓
2013/08/18
554
3
网站布局拖拽实现的疑问

我想实现如下拖拽效果,那么怎么样保存数据库呢? 1,用户可以自己设计html模板。然后制定:拖拽区域(如)无限制个数。 2,然后通过程序(php)读出这些拖拽区。在编辑界面时候,可以往这些...

落舞者
2013/07/10
709
5

没有更多内容

加载失败,请刷新页面

加载更多

一套基于SpringBoot+Vue+Shiro 前后端分离 开发的代码生成器

一、前言 最近花了一个月时间完成了一套基于Spring Boot+Vue+Shiro前后端分离的代码生成器,目前项目代码已基本完成 止步传统CRUD,进阶代码优化: 该项目可根据数据库字段动态生成 controll...

郑清
20分钟前
2
0
javascript-十六进制随机颜色

<script> // 编写一个函数,获得一个十六进制的随机颜色的字符串(如#20CD4F) // function randomColor(){ // var r = random(0,255).toString(16); // var g = random(0,255).toString(16......

ACKo
22分钟前
2
0
springBoot +mybatis 出现sql 语句在数据库可以查询到,但是赋值到实体类上就没有的情况?

1.不要老是反复查看自己是否写错了,为啥有的能出来有的出不来? 可以查看配置文件中是否配置全: 如果在application.yml 文件中是如下配置: mybatis: mapper-locations: classpath:mapp...

kuchawyz
34分钟前
2
0
正则表达式

一、RegExp对象 进行验证和查找的API 1、创建对象: (1)用/创建(直接量):var reg=/正则/ig,表达式固定不变时使用 (2)用new创建:var reg=new RegExp(‘正则’,‘ig’),表达式需要...

wytao1995
35分钟前
2
0
实战限流(guava的RateLimiter)

关于限流 常用的限流算法有漏桶算法和令牌桶算法,guava的RateLimiter使用的是令牌桶算法,也就是以固定的频率向桶中放入令牌,例如一秒钟10枚令牌,实际业务在每次响应请求之前都从桶中获取...

程序员欣宸
36分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部