一、起因&思路

不知不觉,已经好几天没写博客了。。。近来除了研究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分别做成了一个组件:

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;
this.props.callbackParent(newState);
}

这样,在子组件中就启动了拖拽开关,并且已经更新了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

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

React.js实现原生js拖拽效果及思考的更多相关文章

  1. js div浮动层拖拽效果代码

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  2. 原生js实现模块来回拖拽效果

    代码比较冗余,还没来得及做整理,往见谅. 主要用到的 JS 事件有: onmousedown:鼠标点下事件 onmousemove:鼠标移动事件 onmouseup:鼠标放开事件 具体代码如下: &l ...

  3. 【JS】原生实现拖拽

    <!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8 ...

  4. 再谈React.js实现原生js拖拽效果

    前几天写的那个拖拽,自己留下的疑问...这次在热心博友的提示下又修正了一些小小的bug,也加了拖拽的边缘检测部分...就再聊聊拖拽吧 一.不要直接操作dom元素 react中使用了虚拟dom的概念,目 ...

  5. 原生js简单实现拖拽效果

    实现弹窗拖拽效果的原理是:按下鼠标并移动——拖拽移动物体,抬起鼠标——停止移动.主要触发三个事件:onmousedown.onmousemove以及onmouseup: 首先搭建结构:一个宽350px ...

  6. js拖拽效果

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  7. JS实现漂亮的窗口拖拽效果(可改变大小、最大化、最小化、关闭)

    转自<JS实现漂亮的窗口拖拽效果(可改变大小.最大化.最小化.关闭)>:http://www.jb51.net/article/73157.htm   这篇文章主要介绍了JS实现漂亮的窗口 ...

  8. js实现本地图片文件拖拽效果

    如何拖拽图片到指定位置,具体方法如下 在从本地上传图片的时候,如果使用拖拽效果,想想应该是更加的高大上,下面直接上js代码 完整代码: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 ...

  9. 用JS实现版面拖拽效果

    类似于这样的一个版面,点击标题栏,实现拖拽效果. 添加onmousedown事件 通过获取鼠标的坐标(clientX,clientY)来改变面板的位置 注意:面板使用绝对定位方式,是以左上角为参考点, ...

随机推荐

  1. 【原创分享·支付宝支付】HBuilder打包APP调用支付宝客户端支付

    前言 最近有点空余时间,所以,就研究了一下APP支付.前面很早就搞完APP的微信支付了,但是由于时间上和应用上的情况,支付宝一直没空去研究.然后等我空了的时候,发现支付宝居然升级了支付逻辑,虽然目前还 ...

  2. CENTOS 6.5 平台离线编译安装 PHP5.6.6

    一.下载php源码包 http://cn2.php.net/get/php-5.6.6.tar.gz/from/this/mirror 二.编译 编译之前可能会缺少一些必要的依赖包,加载一个本地yum ...

  3. Go结构体实现类似成员函数机制

    Go语言结构体成员能否是函数,从而实现类似类的成员函数的机制呢?答案是肯定的. package main import "fmt" type stru struct { testf ...

  4. Web安全相关(三):开放重定向(Open Redirection)

    简介 那些通过请求(如查询字符串和表单数据)指定重定向URL的Web程序可能会被篡改,而把用户重定向到外部的恶意URL.这种篡改就被称为开发重定向攻击.   场景分析 假设有一个正规网站http:// ...

  5. H3 BPM让天下没有难用的流程之技术体系

    一.技术架构 H3 BPM 基于微软.NET 技术架构,采用C#语言开发,以高开放.高扩展.高性能为核心准则,遵循分层的设计原理,结合最新的B/S 以及智能手机应用开发技术研发的. 图:H3 BPM  ...

  6. myrocks复制中断问题排查

    背景 mysql可以支持多种不同的存储引擎,innodb由于其高效的读写性能,并且支持事务特性,使得它成为mysql存储引擎的代名词,使用非常广泛.随着SSD逐渐普及,硬件存储成本越来越高,面向写优化 ...

  7. Android Bitmap 和 ByteArray的互相转换

    Android Bitmap 和 ByteArray的互相转换 移动平台图像处理,需要将图像传给native处理,如何传递?将bitmap转换成一个 byte[] 方便传递也方便cpp代码直接处理图像 ...

  8. 让 asp.net 在 mac 上飞

    .NET 不跨平台一直饱受争议,虽然微软前端时间放出些消息,要支持.NET跨平台的发展,但是微软一直坚持着不主动.不拒绝.不负责的三不态度,仍然用一种软件帝国的心态,折腾着一些毫无新意的东西.微软想要 ...

  9. Dubbo学习小记

    前言 周一入职的新公司,到了公司第一件事自然是要熟悉新公司使用的各种技术,搭建本地的环境. 熟悉新公司技术的过程中,首先就是Maven,这个前面已经写过文章了,然后就是Dubbo----公司的服务都是 ...

  10. AngularJS实例实战

    学习了这么多天的AngularJS,今天想从实战的角度和大家分享一个简单的Demo--用户查询系统,以巩固之前所学知识.功能需求需要满足两点 1.查询所有用户信息,并在前端展示 2.根据id查询用户信 ...