js 实现多重罗盘转动

2023-11-15

引子

这几天一直在忙一个可滑动的转盘的demo,网上也有类似的例子,但是根据老板的需求来改他们的代码,还不如重新写个完全符合需求的插件。
想法很美好,但是新手上路...

效果链接文末

需求

image

这个demo给的非常简单,能转动的地方有三处,内盘、外盘和指针,这三个上的集合的交集产生一个链接,通过中间的按钮跳转。

这个需求乍一看老简单老简单的,但是作为一个菜鸡第一次上道,堪比开碰碰车,头破血流。

分析

在做之前,也是根据自己的理解来写的旋转角度问题:

  • 转盘转动的做法是:设定圆心为转动原点,动态的修改旋转角度;
  • 在touchmove 计算两点与中心点的角度。

在旋转上大体上需要明白的也就这两点,但是在实际计算角度上却有很多问题。

弯道1之计算角度

计算角度首先要用到的一个数学方法就是反函数,在JS中表示反函数的方法有两个:

  • Math.atan
  • Math.atan2

说实话它们两个的区别对于本次demo真没有测出什么差异来,但是相比 atan在y特别大的时候会有误差产生的情况下,果断选择了atan2

(function($){
        $.fn.CompassRotate=function(options){
            var defaults={
                trigger:document,           
                centerX:0,                     
                centerY:0,                      
                debug:false
            },_this=this;
            var ops=$.extend(defaults,options);
            function Init(){
                //初始化圆心点
                if(ops.centerX==0 && ops.centerY==0){
                    ops.centerX=_this.offset().left+_this.width()/2;
                    ops.centerY=_this.offset().top+_this.height()/2
                }
                $(ops.trigger).on("touchstart",function(event){
                    $(document).on("touchmove",movehandle);
                });
                $(ops.trigger).on("touchend",function(event) {
                    $(document).unbind("touchmove");
                });
            }
            //鼠标移动时处理事件
            function movehandle(event){
                var touch = event.originalEvent.targetTouches[0];
                var dis = angle(ops.centerX,ops.centerY,touch.pageX, touch.pageY);

                if(ops.debug) console.log(ops.centerX+"-"+ops.centerY+"|"+touch.pageX+"-"+touch.pageY+" "+dis);

                rotate(dis);
            }
            //计算两点的线在页面中的角度
            function angle(centerx, centery, endx, endy) {
                var diff_x = endx - centerx,
                    diff_y = endy - centery;
                var c=360 * Math.atan2(diff_y , diff_x) / (2 * Math.PI);
                c=c<=0?(360+c):c;

                return c; 
            }
            //设置角度
            function rotate(angle,step){
                $(_this).css("transform", "translate3d(-50%,-50%,0) rotateZ(" + angle + "deg)");
            }
            // 指针指向角度变化和生成url
            function angleOrLink(angle) {
                Angle = angle;
            }
            Init();
        };
    })(jQuery);
    $(".box").CompassRotate({trigger:$(".box"),debug:true});复制代码

啰里啰嗦不如直接贴上代码,大家看得更明白些。

弯道2之区域集合变化

做过转盘抽奖的大佬都知道,每个奖品都对应一个角度集合,指针所转的角度[0,360]看看对应落在哪个集合上,而这个转盘也是同理,但是唯一不同的地方在于,内盘和外盘的集合是可变化的,并不是固定不变的。

var insideCollection = [
    {
        /* GC+S1 */
        min: 270,
        max: 360,
        reverse: false,
        mark: 's1gc'
    },
    {
        /* BC+AT */
        min: 0,
        max: 45,
        reverse: false,
        mark: 'bcat'
    },
    {
        /* BC+GT */
        min: 45,
        max: 90,
        reverse: false,
        index: 'bcgt'
    },
    {
        /* mCRC+FOLFOX */
        min: 90,
        max: 180,
        reverse: false,
        mark: 'mCRC'
    },
    {
        /* eCRC+化疗 */
        min: 225,
        max: 270,
        reverse: false,
        mark: 'eCRC1'
    },
    {
        /* eCRC+FOLFOX */
        min: 180,
        max: 225,
        reverse: false,
        mark: 'eCRC2'
    }

];
var outsideCollection = [
    {
        /* 研究 */
        min: 270,
        max: 342,
        reverse: false,
        mark: '研究'
    },
    {
        /* 指南 */
        min: 342,
        max: 54,
        reverse: true,
        mark: '指南'
    },
    {
        /* 竞品 */
        min: 54,
        max: 126,
        reverse: false,
        mark: '竞品'

    },
    {
        /* 资料 */
        min: 126,
        max: 198,
        reverse: false,
        mark: '资料'
    },
    {
        /* 机制 */
        min: 198,
        max: 270,
        reverse: false,
        mark: '机制'
    }

];复制代码

min,max不用说了,就是表示集合,reverse 这个属性代表的是什么呢?
在做区间划分的时候,角度的变化永远都是0-360°,“0==360”。所以,当某个集合的区间是[340,25]的时候该怎么表示呢?
当然,每次转动都有且只有一个集合会面临这样的情况,所以我用一个属性来表示这个区间跨角度了。

// 转盘区间分布变化
function collectionChange(angle,array) {
    array.forEach(function (ele,index) {
        ele.reverse = false;
    });
    array.forEach(function (ele,index) {
        ele.min = (Number(angle)+Number(ele.min))%360;
        ele.max = (Number(angle)+Number(ele.max))%360;
        if(ele.min > ele.max){
            ele.reverse = true;
        }
    });
    console.log(array)
}复制代码

mark 也不用多谈,选中了集合该表示表示了呀。

代码贴到这也基本完成了大体功能,最后也是在点击链接的时候根据内外盘的 mark 来匹配链接了:

$('#compass_5').on('click',function(){
    var angle = Angle;
    // 内盘标号
    var link = contrast(insideCollection) + contrast(outsideCollection);
    console.log(link);
    function contrast(array){
        var link ;
        array.forEach(function (ele,index) {
            if(angle >= ele.min%360 && angle <= (ele.max%360 ==0?360:ele.max%360)){
                link = ele.mark;
            }
            else if(ele.reverse){
                if(angle<=360 && angle >=270){
                    if(angle >= ele.min%360 && angle <= (ele.max%360 ==0?360:ele.max%360+360)){
                        link = ele.mark;
                    }
                }
                else if(angle>=0&&angle<=90){
                    if(angle+360 >= ele.min%360 && angle+360 <= (ele.max%360 ==0?360:ele.max%360+360)){
                        link = ele.mark;
                    }
                }
            }
        });
        return link;
    }
})复制代码

弯道3之坑王之王

上面说到功能大体完成了,那只是按部就班的在轮盘上只选择一个点进行转动,如果在不同位置多次转动,发现整个转盘瘫痪了——mark对应不上了。

做这个demo第一步,我是从一个简单的指针转盘开始起手的,也就是完成一个转动指针的基本操作,所以整套流程下来是可行的,因为这个指针订好了转动圆心,它的可选区域仅仅是辣么一小块,所以根本看不到为后面埋了多大坑。

反函数计算角度问题

var c=360 * Math.atan2(diff_y , diff_x) / (2 * Math.PI); 
// c [-180,180];
c=c<=0?(360+c):c;
// c [0,360];复制代码

这样计算角度对于指针来说,没什么问题,但是对于转盘上来说可能就是个噩梦。

因为它的着落点并不确定。

导致当你点到不同区域的时候,它会给你直接将转动的角度赋值,所以会造永远是中间那条线跟着手指滑动。

image

坑王之王链接(chrome调试器里查看)

这样的操作遇到的坑就是起始位置随着手指的变动会导致各个区域的区间也应该发生相应的变化,所以在 touchstart 还要进行一步操作,计算上一次结束位置与目前位置的夹角,然后再次更改区间变化。

$(ops.trigger).on("touchstart",function(event){
    var touch = event.originalEvent.changedTouches[0];
    var dis = angle(ops.centerX,ops.centerY,touch.pageX, touch.pageY);
    startAngle = dis;
    //再次滑动转盘后的角度与上一次结束角度不一致的情况(内盘)
    if(startAngle != ops.initAngle_in && ops.initAngle_in != 0){
        if(ops.initAngle_in>startAngle){
            insideDishAngleChangeSecondary((Number(startAngle+360)-ops.initAngle_in));
        }
        else if(ops.initAngle_in< startAngle){
            insideDishAngleChangeSecondary((startAngle-ops.initAngle_in));
        }

    }
    $(document).on("touchmove",movehandle);
});复制代码

修改后的罗盘

上一版的罗盘基本操作是将错就错,产生了一系列bug,虽然都克服了一系列bug,但还是都是在挖坑,只不过坑是平行挪动,这个坑挖不动了换了个方向继续挖而已。

岔路

重新审视自己的思路时,才发现自己是多么的蠢。

之前的算法是手指指在哪里,开始点为0,结束点为所指点 ,在 touchmove 给罗盘赋角度值时,直接将两点形成的角度赋给了罗盘 rotate(angle)。之后的一系列操作都是为这个地方买单,无论是重新写个函数记录变换角度在 touchmove 开始之前赋给罗盘分布区间、还是中心点僵硬随着手指转动。

重新思考了下罗盘的转法,有了之前的铺垫,所以思路也变得特别清晰了。
实现这个需求,记录的数据一共有三个:

  • actual_angle :开始点和结束点与中心点的夹角,这就是罗盘每次转动的度数,该值需要累加;
  • addAngle :每次转动结束后,需要给罗盘分布区间增加的值,该值等同于 actual_angle
  • startAngletouchstart 时手指着落点,即开始点。
$.fn.RotateH=function(options){
    var defaults={
        trigger:document,           
        centerX:0,                      
        centerY:0,                      
        debug:false
    },_this=this;
    var ops=$.extend(defaults,options);
    var startAngle,addAngle,
        actual_angle = 0;
    //初始化
    function Init(){
        //初始化圆心点
        if(ops.centerX==0 && ops.centerY==0){
            ops.centerX=_this.offset().left+_this.width()/2;
            ops.centerY=_this.offset().top+_this.height()/2
        }
        $(ops.trigger).on("touchstart",function(event){
            var touch = event.originalEvent.changedTouches[0];
            var dis = angle(ops.centerX,ops.centerY,touch.pageX, touch.pageY);
            startAngle = dis;
            $(document).on("touchmove",movehandle);
        });
        $(ops.trigger).on("touchend",function(event) {

            var touch = event.originalEvent.changedTouches[0];
            var dis = angle(ops.centerX,ops.centerY,touch.pageX, touch.pageY);

            //每次转动的角度
            if(dis >=startAngle){
                //罗盘累加转动度数
                actual_angle += (dis-startAngle);
                //区间每次增加度数
                addAngle = (dis-startAngle);
            }
            else if(dis <startAngle){
                actual_angle += (dis+360-startAngle);
                addAngle = (dis+360-startAngle)
            }
            if(ops.collection) collectionChange(addAngle,ops.collection);
            else angleOrLink(dis);
            $(document).unbind("touchmove");
        });
    }
    //鼠标移动时处理事件
    function movehandle(event){

        // 获取两点之间角度
        var touch = event.originalEvent.targetTouches[0];
        var dis = angle(ops.centerX,ops.centerY,touch.pageX, touch.pageY);
        var Angle = 0;

        if(ops.debug) console.log(ops.centerX+"-"+ops.centerY+"|"+touch.pageX+"-"+touch.pageY+" "+dis);

        if(ops.pointer){
            rotate(dis);
        }
        else {
            //每次转动的角度
            if(ops.debug) {
                console.log("——————————————————————");
                console.log('上次转动的角度:'+actual_angle);
            }
            if(dis >=startAngle){
                Angle = dis-startAngle;
                if(ops.debug) {
                    console.log("转动角度:"+Angle);
                    console.log("实际转动角度:"+(Angle+actual_angle));
                }
                rotate((Angle+actual_angle));
            }
            else if(dis <startAngle){
                Angle = dis-startAngle+360;
                if(ops.debug){
                    console.log("转动角度:" + Angle);
                    console.log("实际转动角度:"+(Angle+actual_angle));
                }
                rotate((Angle+actual_angle));
            }
        }
    }
    //计算两点的线在页面中的角度
    function angle(centerx, centery, endx, endy) {
        var diff_x = endx - centerx,
            diff_y = endy - centery;
        var c=360 * Math.atan2(diff_y , diff_x) / (2 * Math.PI);
        c=c<=0?(360+c):c;

        return c;
    }
    //设置角度
    function rotate(angle,step){
        $(_this).css("transform", "translate3d(-50%,-50%,0) rotateZ(" + angle + "deg)");
    }
    // 转盘区间分布变化
    function collectionChange(angle,array) {
        array.forEach(function (ele,index) {
            ele.reverse = false;
        });
        array.forEach(function (ele,index) {
            ele.min = (Number(angle)+Number(ele.min))%360;
            ele.max = (Number(angle)+Number(ele.max))%360;
            if(ele.min > ele.max){
                ele.reverse = true;
            }
        });
        if(ops.debug) console.log(array);
    }
    // 指针所转角度
    function angleOrLink(angle) {
        Angle = angle;
    }
    Init();
};复制代码

效果链接地址:perfectCompass.github.io (这是个ipad demo,请在chrome调试器查看)

github 地址:github.com/suiyang1714…

总结

这个demo最终是自己靠时间磨出来了的,没有特别高的技术含量,主要是在这个过程中思考。如果一开始想明白了每一步要干什么,也不会拐那么多的弯道了。
我一开始的想法是,罗盘先能转动,然后再考虑的区间变化,出现问题解决解决问题,没有看到为什么会出现这个问题。基本是走一步看一步。心好累。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

js 实现多重罗盘转动 的相关文章

  • 如何确定并打印 jQuery 版本?

    是否有一个 jQuery 函数可以返回当前加载的 jQuery 版本 你可以使用这个 fn jquery or if you re using noConflict jQuery fn jquery 当 jQuery 构建时它会自动更新 定
  • 使用 jQuery 更改父元素样式

    我有下一个 html 设置 div class one div class two a href class three Click a div div 我想更改具有类的元素的背景颜色 one当我点击元素时 three使用 jQuery 这
  • axios 请求中未发送正文数据

    我试图通过 axios 请求将数据发送到我的后端脚本 但正文看起来是空的 这是前端发送的请求 axios request method GET url http localhost 4444 next api headers Authori
  • 类型错误:类扩展值未定义不是函数或 null

    尝试创建这些实体时出现以下错误 TypeError Class extends value undefined is not a function or null 我假设这与循环依赖有关 但是在使用表继承和一对多关系时应该如何避免这种情况
  • JQuery datepickers-从开始日期设置结束日期

    使用了两个 Jquery 日期选择器 StartDate 和 EndDate
  • Mapbox GL 中的 MaxBounds 和自定义非对称填充

    我有一个 Mapbox GL JS 应用程序 在地图上显示一些小部件 为了确保地图上的任何内容都不会被它们隐藏 我使用以下命令添加了一些填充map setPadding 这是一个不对称的 在我的例子中左边比右边大 它按预期工作 例如fitB
  • 如何在不阻止触摸启动的情况下防止“过度滚动历史导航”?

    我正在实现基于滑动的导航 但我在使用 Chrome 时遇到了麻烦 当页面向右拖动时 会触发新实现的功能 过度滚动历史导航 从而导致跳回 到 历史 1 为了防止这种情况 我必须打电话 preventDefault on touchstart
  • 如何使 d3 饼图响应式?

    我有一个 PIE 图表 它工作正常 但我无法使其具有响应能力和可调整大小 我需要它与移动浏览器和 iPad 等兼容 div div
  • 此页面上的脚本导致 ie 运行缓慢

    问题就在标题中 IE 行为异常 并说有一个脚本运行缓慢 FF 和 Chrome 没有这个问题 我怎样才能找到问题所在 那个页面有很多JS 手动检查不是一个好主意 EDIT 这是我正在处理的一个项目的页面 但我需要一个工具来查找问题 End
  • API 使用令牌向 odoo 进行身份验证

    我想使用令牌从 Express 应用程序向 Odoo 进行身份验证 我在用odoo xmlrpc https www npmjs com package odoo xmlrpc连接 Odoo 的节点模块 我的快递应用程序 Odoo 要求 A
  • React JS 服务器端问题 - 找不到窗口

    你好 我正在尝试在我的reactJS项目中使用react rte 我有服务器端渲染 每次我想使用这个包时 我都会得到 return msie 6 9 b test window navigator userAgent toLowerCase
  • 如何在 Web 服务器上设置 gzip 压缩?

    我有一个嵌入式网络服务器 总共有 2 兆空间 通常 您使用 gzip 文件对客户端有利 但这会节省我们在服务器上的空间 我读到你可以只 gzip js 文件并将其保存在服务器上 我在 IIS 上测试过 但没有任何运气 为了使这项工作成功 我
  • 如果一个对象结构与另一个对象结构不匹配/不匹配,如何引发异常

    我将读取格式正确的用户输入对象 也就是说 输入对象现在可以具有接口中未定义的任何键或子结构 如果用户提供了无效的对象 我如何抛出异常 预定义接口 export interface InputStructureInterface tableN
  • javascript 是否有等效的 __repr__ ?

    我最接近Python的东西repr这是 function User name password this name name this password password User prototype toString function r
  • Javascript 浮点乘以 100 仍然有错误

    我有一个货币字段的文本输入 我在字段中输入 33 91 并在尝试使用 乘以 100 技术时得到以下结果 var curWth parseInt trans withdraw index val 100 3390 var curWth par
  • 扩展 RegExp 以获取文件扩展名

    我知道 已经有很多基于 RegExp 的解决方案 但是我找不到适合我需求的解决方案 我有以下函数来获取 URL 的各个部分 但我还需要文件扩展名 var getPathParts function url var m url match w
  • 以角度访问窗口 TemplateUrl 内的范围

    我的模式有一个 windowTemplateUrl 如下 div class modal fade div class modal dialog div class modal content square btn div div div
  • 确定 Javascript 中的日期相等性

    我需要找出用户在 Javascript 中选择的两个日期是否相同 日期以字符串 xx xx xxxx 形式传递给该函数 这就是我需要的全部粒度 这是我的代码 var valid true var d1 new Date datein val
  • 将多维数组转换为单数组(Javascript)

    我有一个对象数组 来自 XLSX js 解析器 因此其长度和内容各不相同 表示已给予项目的资助 简化后 它看起来像这样 var grants id p 1 location loc 1 type A funds 5000 id p 2 lo
  • 如何在 Jquery/Javascript 中绑定模糊和更改,但只触发一次函数?

    我试图在选择元素更改时触发函数 由于 Ipad 在 on change 方面遇到问题 我还想绑定到 blur 这在 Ipad 上工作得很好 但是我不希望两个事件都触发该函数两次 所以我需要某种挂钩来确保两个事件是否都触发change and

随机推荐

  • 云存储服务器的安装文件,云存储服务器的安装文件

    云存储服务器的安装文件 内容精选 换一换 安装传输工具在本地主机和Windows云服务器上分别安装数据传输工具 将文件上传到云服务器 例如QQ exe 在本地主机和Windows云服务器上分别安装数据传输工具 将文件上传到云服务器 例如QQ
  • 【三维重建】Ubuntu18.04安装COLMAP

    Ubuntu18 04安装COLMAP 文章目录 Ubuntu18 04安装COLMAP 前言 安装COLMAP 安装CUDA cuDNN 安装依赖项 安装Ceres优化库 安装glog 可选 配置并编译COLMAP 运行COLMAP 总结
  • 数据结构-线性表之堆栈

    什么是栈 是一种数据结构 能够实现后进先出的一种业务场景 即栈中的元素被处理时 按后进先出的顺序进行 所以栈又叫做后进先出表 LIFO 例子 生活中的叠放在厨房桌子上的碗就是一种栈结构 放的时候只能把碗放在最上面 取的时候只能从最上面开始取
  • python+Visual studio code配置Selenium环境

    python Visual studio code配置Selenium环境 使用pip命令安装selenium Visual studio code控制台中直接输入 python m pip install selenium 查看是否安装s
  • 如何使用OpenAI进行embedding句子后,输入新句子比较

    import openai import numpy as np from sklearn metrics pairwise import cosine similarity Set up OpenAI API credentials op
  • 权衡问题---学习了微服务各大常用组件的一点思考

    1 没有什么技术可以完全通用 只要深度使用一定需要从业务出发对技术进行权衡 从几个例子出发 1 ES的分片机制天生支持分布式 同时也带来了分布式了弊端 排序和算分问题 搜索的场景需要严格的定制化 通过对mapping的修改可以支持算分的调整
  • vtk9.2.6编译记录

    1 cmake gui打开 搜索qt相关 将默认选项改成yes 重新configure之后 会出现新的选项 比如Qt目录 确认没有问题之后 Generate生成sln解决方案后 用vs编译 2 配置CMAKE INSTALL PREFIX
  • Git 将已有项目上传新建仓库

    Git 将已有项目上传新建仓库 一 gitee或github创建空白仓库 记得保留下git链接 https gitee com huadeng863 git 二 在已有项目目录内删除已有git readme 如果有的话 将项目中的 git
  • buildroot 编译出错

    编译buildroot的时候出现如下错误 Incorrect selection of kernel headers expected 4 1 x got 4 0 x package pkg generic mk 228 recipe fo
  • phpstudy 8.1下载安装和简单使用教程

    phpstudy下载 去官网下载PHPStudy 选择自己电脑对应的系统版本 在首页导航选择windows版 弹出下拉框点击phpStudy 客户端 进入下载界面 点击下载 接着选择64位 然后就开始下载了 PHPStudy安装 解压下载好
  • stm32---用外部中断实现红外接收器

    一 红外遥控的原理 红外遥控是一种无线 非接触控制技术 具有抗干扰能力强 信息传 输可靠 功耗低 成本低 易实现等显著优点 被诸多电子设备特别是 家用电器广泛采用 并越来越多的应用到计算机系统中 红外遥控通信系统一般由红外发射装置和红外接收
  • 无意中发现的方法:一招让你的 IntelliJ Idea 飞起来

    一 引言 最近工作中使用到了 IntelliJ Idea 说实在的 不太熟悉这个 IDE 不知道为什么 对比 Eclipse 和 IntelliJ Idea 在我的电脑上 竟然是 IntelliJ Idea 比 Eclipse 运行的更卡一
  • python炫酷gui界面_如何炫酷的使用Python

    Python很酷 真的很酷 但是 我们中的很多人都是从不同的编程语言开始的 尽管我们确实很容易地掌握了Python 但是我们仍然不擅长以pythonic的方式做事 本文介绍了我几年来学到的一些技巧和相同的实际示例 希望你喜欢它 1 有用的键
  • android aidl出现无法import

    当采用eclipse 写aidl时出现couldn t find import for class 原因是你import的包没有在framework aidl里parcelable过 所以解决办法很简单 找到对应api level的fram
  • 软件开发十本书

    几年前 总结了 软件测试十本书 对从事软件质量验证和确认工作的基础知识进行简介 软件花钱最多 耗时最长 投入人力和精力最大的 是在开发过程 根据个人经验 推荐软件开发相关的十本书 供入门和进阶参考 坐等拍砖员 1 代码大全 软件开发世界的
  • Java 多线程同步:volatile 关键字

    多线程基础知识 Java 内存模型 Java 中的堆内存用来存储对象的实例 堆内存是被所有线程共享的运行时内存区域 因此 它存在可见性的问题 而局部变量 方法定义的参数则不会在线程之间共享 它们不会有内存可见性问题 也不受内存模型的影响 J
  • MySQL Cluster

    MySQL Cluster数据分布 分区 两阶段提交协议及事务资源 2013 12 11 15 00 28 分类 MySQL FROM http www zrwm com p 3210 Posted on 2013 年 3 月 8 日 by
  • https网络编程——openssl安装(两个软链接库libssl、libcrypto)、openssl命令行方式实现CA和证书、介绍SSH

    参考 openssl安装方式 Ubuntu下 地址 https qingmu blog csdn net article details 115454254 spm 1001 2014 3001 5502 目录 官方网站 1 解压 2 编译
  • stable diffusion webui安装与使用(官方超简单教程)

    预备依赖 下载miniconda 教程参考 https blog csdn net weixin 43828245 article details 124768518 安装git 参考教程 https blog csdn net weixi
  • js 实现多重罗盘转动

    引子 这几天一直在忙一个可滑动的转盘的demo 网上也有类似的例子 但是根据老板的需求来改他们的代码 还不如重新写个完全符合需求的插件 想法很美好 但是新手上路 效果链接文末 需求 image 这个demo给的非常简单 能转动的地方有三处