0

0

通过JavaScript实现移动物体代码

零下一度

零下一度

发布时间:2017-04-17 17:56:45

|

2783人浏览过

|

来源于php中文网

原创

交互动画的一个主要目标是创建出流畅的用户体验,其中大多数的用户交互都是通过鼠标和触摸屏实现的。

在这篇博文中,我想分享一些JS对于物体移动的常见用法,包括拖拽和投掷效果。

一. 使用鼠标事件

可以将一个鼠标单击事件分解成两个事件:鼠标按下事件和按键弹起事件。通常情况下这两个事件是同时发生的。不过,有时鼠标按下后,鼠标还会移动一段时间才弹起,这种操作称为拖曳,即按下、移动、在释放。

在canvas动画中,鼠标事件只能被HTML DOM树上的canvas元素所捕获,因此,我们需要手动计算出鼠标事件在canvas上的发生位置,并判断出它是否发生在哪个绘制到canvas的物体 上。需要关注的鼠标事件有:mousedown、mousemove和mouseup。具体细节可参考我的相关博文《JavaScript动画详解(一) —— 循环与事件监听》。

二. 使用触摸事件

随着触摸屏设备的流行,我们很可能需要在动画中捕捉用户的触摸事件。虽然触摸屏与鼠标是不同的设备,但幸运的是,在DOM树上捕捉触摸事件与捕捉鼠标事件的差别不大。

立即学习Java免费学习笔记(深入)”;

与鼠标事件mousedown、mousemove和mouseup相对应的触摸事件分别是touchstart、touchend与touchmove。

使用手指与鼠标的一个比较大的区别在于,鼠标始终出现在屏幕上,而手指却不是一直处于触摸状态。常见的做法是,引入自定义属性isPressed,用来告诉我们屏幕上是否有手指在触摸。具体细节可参考我的相关博文《JavaScript动画详解(一) —— 循环与事件监听》。

三. 拖拽事件

拖拽事件包含了三个子事件:鼠标按下、移动、释放。通过不断更新物体的坐标位置使其追随鼠标指针的位置,就可以实现在canvas元素上拖拽物体。 另外还需要一个自定义属性isPressed来标示当前鼠标是否按下,默认为false表示鼠标为弹起状态。实现代码包含以下过程:

1 . 捕捉mousedown事件,判断当前鼠标是否在物体内。当鼠标在物体内按下时,设置isPressed = true;

2 . 捕捉mousemove事件,在处理程序内判断当isPressed = true时,通过不断更新物体的坐标位置使其追随鼠标指针的位置来模拟出鼠标拖拽效果;

3 . 捕捉mouseup事件,将isPressed设置为false;

HTML代码如下:

 

成新网络商城购物系统
成新网络商城购物系统

使用模板与程序分离的方式构建,依靠专门设计的数据库操作类实现数据库存取,具有专有错误处理模块,通过 Email 实时报告数据库错误,除具有满足购物需要的全部功能外,成新商城购物系统还对购物系统体系做了丰富的扩展,全新设计的搜索功能,自定义成新商城购物系统代码功能代码已经全面优化,杜绝SQL注入漏洞前台测试用户名:admin密码:admin888后台管理员名:admin密码:admin888

下载

JavaScript代码如下:

// 创建画球函数
function Ball() {
    this.x = 0;
    this.y = 0;
    this.radius = 20;
    this.fillStyle = "#f85455";
    this.draw = function(cxt) {
        cxt.fillStyle = this.fillStyle;
        cxt.beginPath();
        cxt.arc(this.x, this.y, this.radius,  0, 2 * Math.PI, true);
        cxt.closePath();
        cxt.fill();
    }
}
// 获得当前鼠标位置
function getMouse(ev) {
    var mouse = {
        x: 0,
        y: 0
    };
    var event = ev || window.event;
    if(event.pageX || event.pageY) {
        x = event.x;
        y = event.y;
    }else {
        var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft;
        var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
        x = event.clientX + scrollLeft;
        y = event.clientY + scrollTop;
    }
    mouse.x = x;
    mouse.y = y;

    return mouse;
}

var canvas = document.getElementById("canvas"),
        context = canvas.getContext("2d"),
        ball = new Ball(),
        mouse = {x: 0, y: 0},
        isPressed = false;
ball.x = 20;
ball.y = 20;

// 渲染小球
ball.draw(context);

// 小球拖拽事件
canvas.addEventListener("mousedown", mouseDown, false);
canvas.addEventListener("mousemove", mouseMove, false);
canvas.addEventListener("mouseup", mouseUp, false);

function mouseDown(ev) {
    // 判断当前鼠标是否在小球内
    mouse = getMouse(ev);
    if(Math.pow(mouse.x - ball.x, 2) + Math.pow(mouse.y - ball.y, 2) <= Math.pow(ball.radius, 2)) {
        isPressed = true;
    }
}

function mouseMove(ev) {
    if(isPressed) {
        mouse = getMouse(ev);

        ball.x = mouse.x;
        ball.y = mouse.y;
        context.clearRect(0, 0, canvas.width, canvas.height);
        ball.draw(context);
    }
}

function mouseUp(ev) {
    // 标示鼠标弹起事件
    isPressed = false;
}

    

但是,这个例子是有bug的!很快你就能发现,在拖拽的时候,小球的中心位置都在鼠标位置上,特别是当鼠标单击小球边缘时,会看见小球的中心点突然就跳动到了鼠标光标的位置上了。显然,这显得有点唐突。

 

我们可以稍作改良:

在鼠标按下的时候记录当前鼠标位置与小球中心点位置的偏移量;

// 记录鼠标按下时,鼠标与小球圆心的偏移量
dx = mouse.x - ball.x;
dy = mouse.y - ball.y;

 

在鼠标移动时,用鼠标的当前位置减去鼠标按下时记录的偏移量

ball.x = mouse.x - dx;
ball.y = mouse.y - dy;

 

四. 投掷事件

 

在动画中如何表现投掷呢?用鼠标选中一个物体,拖拽着它向某个方向移动,松开鼠标后,物体沿着拖拽的方向继续移动。

在投掷物体时,必须在拖拽物体的过程中计算物体的速度向量,并在释放物体时将这个速度向量赋给物体。实际上,计算拖拽时物体的速度向量的过程,恰好 与对物体应用速度向量的过程相反。在对物体应用速度向量时,将速度追加到物体原来所在的位置上,从而计算出物体的新位置,这个公式可以写成:旧的位置 + 速度向量 = 新的位置,即速度向量 = 新的位置 – 旧的位置

为了实现投掷行为,需要对前面的代码做一些改动。首先,检查鼠标是否按下,如果按下,用oldX和oldY变量保存小球旧的x、y坐标位置,并更新小球的拖拽速度。

具体JavaScript代码实现如下:

// 创建画球函数
function Ball() {
    this.x = 0;
    this.y = 0;
    this.radius = 20;
    this.fillStyle = "#f85455";
    this.draw = function(cxt) {
        cxt.fillStyle = this.fillStyle;
        cxt.beginPath();
        cxt.arc(this.x, this.y, this.radius,  0, 2 * Math.PI, true);
        cxt.closePath();
        cxt.fill();
    }
}
// 获得当前鼠标位置
function getMouse(ev) {
    var mouse = {
        x: 0,
        y: 0
    };
    var event = ev || window.event;
    if(event.pageX || event.pageY) {
        x = event.x;
        y = event.y;
    }else {
        var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft;
        var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
        x = event.clientX + scrollLeft;
        y = event.clientY + scrollTop;
    }
    mouse.x = x;
    mouse.y = y;

    return mouse;
}

var canvas = document.getElementById("canvas"),
        context = canvas.getContext("2d"),
        ball = new Ball(),
        mouse = {x: 0, y: 0},
        isPressed = false,
        oldX = 0,
        oldY = 0,
        currentX = 0,
        currentY = 0,
        vx = 0,
        vy = 0;
ball.x = 200;
ball.y = 200;

// 声明鼠标按下时,鼠标与小球圆心的距离
var dx = 0,
        dy = 0;

// 渲染小球
ball.draw(context);

// 小球拖拽事件
canvas.addEventListener("mousedown", mouseDown, false);
canvas.addEventListener("mousemove", mouseMove, false);
canvas.addEventListener("mouseup", mouseUp, false);

function mouseDown(ev) {
    // 判断当前鼠标是否在小球内
    mouse = getMouse(ev);
    if(Math.pow(mouse.x - ball.x, 2) + Math.pow(mouse.y - ball.y, 2) <= Math.pow(ball.radius, 2)) {
        isPressed = true;
        // 记录鼠标按下时,鼠标与小球圆心的距离
        dx = mouse.x - ball.x;
        dy = mouse.y - ball.y;
        // 获得小球拖拽前的位置
        mouse = getMouse(ev);
        oldX = mouse.x;
        oldY = mouse.y;
    }
}

function mouseMove(ev) {
    if(isPressed) {
        mouse = getMouse(ev);
        ball.x = mouse.x - dx;
        ball.y = mouse.y - dy;
        context.clearRect(0, 0, canvas.width, canvas.height);
        ball.draw(context);
    }
}

function mouseUp(ev) {
    // 标示鼠标弹起事件
    isPressed = false;
    // 把鼠标与圆心的距离位置恢复初始值
    dx = 0;
    dy = 0;
    // 获得小球拖拽后的位置
    mouse = getMouse(ev);
    currentX = mouse.x;
    currentY = mouse.y;

    // 更新速度向量:速度向量 = 新的位置 - 旧的位置
    vx = (currentX - oldX) * 0.05;
    vy = (currentY - oldY) * 0.05;

    drawFrame();
}

// 缓动动画
function drawFrame() {
    animRequest = window.requestAnimationFrame(drawFrame, canvas);
    context.clearRect(0, 0, canvas.width, canvas.height);
    if(ball.x >= canvas.width - 30 || ball.x <= 30 || ball.y >= canvas.height - 30 || ball.y <= 30) {
        window.cancelAnimationFrame(animRequest);
    }
    ball.x += vx;
    ball.y += vy;
    ball.draw(context);
}

 

 

这个Demo的边界判断还有一些bug,过些日子再修复。

 

五. 总结

物体移动事件可以有很多总运动形式,但是都可以分解为三个单独的事件来控制:按下、移动、释放,在鼠标事件中分别对应的是mousedown、 mousemove和mouseup,在触摸事件中分别对应的是touchstart、touchmove和touchend。通过不断更新物体的坐标位 置使其追随鼠标指针的位置,就可以实现在canvas元素上拖拽和投掷的效果。

相关文章

java速学教程(入门到精通)
java速学教程(入门到精通)

java怎么学习?java怎么入门?java在哪学?java怎么学才快?不用担心,这里为大家提供了java速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
Golang gRPC 服务开发与Protobuf实战
Golang gRPC 服务开发与Protobuf实战

本专题系统讲解 Golang 在 gRPC 服务开发中的完整实践,涵盖 Protobuf 定义与代码生成、gRPC 服务端与客户端实现、流式 RPC(Unary/Server/Client/Bidirectional)、错误处理、拦截器、中间件以及与 HTTP/REST 的对接方案。通过实际案例,帮助学习者掌握 使用 Go 构建高性能、强类型、可扩展的 RPC 服务体系,适用于微服务与内部系统通信场景。

8

2026.01.15

公务员递补名单公布时间 公务员递补要求
公务员递补名单公布时间 公务员递补要求

公务员递补名单公布时间不固定,通常在面试前,由招录单位(如国家知识产权局、海关等)发布,依据是原入围考生放弃资格,会按笔试成绩从高到低递补,递补考生需按公告要求限时确认并提交材料,及时参加面试/体检等后续环节。要求核心是按招录单位公告及时响应、提交材料(确认书、资格复审材料)并准时参加面试。

44

2026.01.15

公务员调剂条件 2026调剂公告时间
公务员调剂条件 2026调剂公告时间

(一)符合拟调剂职位所要求的资格条件。 (二)公共科目笔试成绩同时达到拟调剂职位和原报考职位的合格分数线,且考试类别相同。 拟调剂职位设置了专业科目笔试条件的,专业科目笔试成绩还须同时达到合格分数线,且考试类别相同。 (三)未进入原报考职位面试人员名单。

58

2026.01.15

国考成绩查询入口 国考分数公布时间2026
国考成绩查询入口 国考分数公布时间2026

笔试成绩查询入口已开通,考生可登录国家公务员局中央机关及其直属机构2026年度考试录用公务员专题网站http://bm.scs.gov.cn/pp/gkweb/core/web/ui/business/examResult/written_result.html,查询笔试成绩和合格分数线,点击“笔试成绩查询”按钮,凭借身份证及准考证进行查询。

11

2026.01.15

Java 桌面应用开发(JavaFX 实战)
Java 桌面应用开发(JavaFX 实战)

本专题系统讲解 Java 在桌面应用开发领域的实战应用,重点围绕 JavaFX 框架,涵盖界面布局、控件使用、事件处理、FXML、样式美化(CSS)、多线程与UI响应优化,以及桌面应用的打包与发布。通过完整示例项目,帮助学习者掌握 使用 Java 构建现代化、跨平台桌面应用程序的核心能力。

65

2026.01.14

php与html混编教程大全
php与html混编教程大全

本专题整合了php和html混编相关教程,阅读专题下面的文章了解更多详细内容。

36

2026.01.13

PHP 高性能
PHP 高性能

本专题整合了PHP高性能相关教程大全,阅读专题下面的文章了解更多详细内容。

75

2026.01.13

MySQL数据库报错常见问题及解决方法大全
MySQL数据库报错常见问题及解决方法大全

本专题整合了MySQL数据库报错常见问题及解决方法,阅读专题下面的文章了解更多详细内容。

21

2026.01.13

PHP 文件上传
PHP 文件上传

本专题整合了PHP实现文件上传相关教程,阅读专题下面的文章了解更多详细内容。

35

2026.01.13

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 3.7万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 2.2万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 2.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号