使用Raphael绘制流程图,自绘动态箭头,可拖动,有双击事件,纯前端,兼容各种浏览器

简介: 关于Raphaël Raphaël是一个在网页上绘图的js类库,非常小压缩版只有89k左右 官方宣称兼容各种主流浏览器,据笔者测试在IE6下尚有一些问题(不过这些与本文无关) 他是使用js来创建vml或svg来绘图的 缘起 项目中不能使用Silverlight或者flash来解决绘图和拖动...

关于Raphaël

Raphaël是一个在网页上绘图的js类库,非常小压缩版只有89k左右

官方宣称兼容各种主流浏览器,据笔者测试在IE6下尚有一些问题(不过这些与本文无关)

他是使用js来创建vml或svg来绘图的

缘起

项目中不能使用Silverlight或者flash来解决绘图和拖动的问题

而且为了项目效果较好,要求拖动的时候箭头能动态改变起点和重点,别且箭头要改变方向

所以只能考虑JS了

效果

image

演示

http://www.mrlh.net/flowchart/demo.htm[已经不能访问了]

源码

引用

    <script language="javascript" type="text/javascript" src="raphael.js"></script>
    <script language="javascript" type="text/javascript" src="jquery.js"></script>
 

这两个东西是不相干的,引用先后顺序也无所谓

页面加载完成后的代码

        $(function () {
            //用来存储节点的顺序
            var connections = [];
            //拖动节点开始时的事件
            var dragger = function () {
                this.ox = this.attr("x");
                this.oy = this.attr("y");
                this.animate({ "fill-opacity": .2 }, 500);
            };
            //拖动事件
            var move = function (dx, dy) {
                var att = { x: this.ox + dx, y: this.oy + dy };
                this.attr(att);
                $("#test" + this.id).offset({ top: this.oy + dy + 10, left: this.ox + dx + 10 });
                for (var i = connections.length; i--; ) {
                    r.drawArr(connections[i]);
                }
            };
            //拖动结束后的事件
            var up = function () {
                this.animate({ "fill-opacity": 0 }, 500);
            };
            //创建绘图对象
            var r = Raphael("holder", $(window).width(), $(window).height());
            //绘制节点
            var shapes = [
                        r.rect(190, 100, 60, 40, 4),
                        r.rect(290, 80, 60, 40, 4),
                        r.rect(290, 180, 60, 40, 4),
                        r.rect(450, 100, 60, 40, 4)
                     ];
            //定位节点上的文字
            $("#test1").offset({ top: 100 + 10, left: 190 + 10 });
            $("#test2").offset({ top: 80 + 10, left: 290 + 10 });
            $("#test3").offset({ top: 180 + 10, left: 290 + 10 });
            $("#test4").offset({ top: 100 + 10, left: 450 + 10 });
            //为节点添加样式和事件,并且绘制节点之间的箭头
            for (var i = 0, ii = shapes.length; i < ii; i++) {
                var color = Raphael.getColor();
                shapes[i].attr({ fill: color, stroke: color, "fill-opacity": 0, "stroke-width": 2, cursor: "move" });
                shapes[i].id = i + 1;
                shapes[i].drag(move, dragger, up);
                shapes[i].dblclick(function () { alert(this.id) })
            }
            //存储节点间的顺序
            connections.push(r.drawArr({ obj1: shapes[0], obj2: shapes[1] }));
            connections.push(r.drawArr({ obj1: shapes[1], obj2: shapes[2] }));
            connections.push(r.drawArr({ obj1: shapes[2], obj2: shapes[3] }));
        });
 

这些代码注释比较详细,就不多说了

在这些代码中涉及到操作的界面元素HTML代码如下

<body>
    <div id="holder">
    </div>
    <div id="test1" class="test">
        测试1</div>
    <div id="test2" class="test">
        测试2</div>
    <div id="test3" class="test">
        测试3</div>
    <div id="test4" class="test">
        测试4</div>
</body>
 

其中关键元素的样式如下

        #holder
        {
            top: 0px;
            left: 0px;
            right: 0px;
            bottom: 0px;
            position: absolute;
            z-index: 999;
        }
        test
        {
            position: absolute;
            width: 80px;
            height: 30px;
            top: 0px;
            z-index: 0;
        }
 

在拖动事件中,动态改变了节点文本元素的位置

并且重绘了节点和箭头

drawArr是一个自定义方法,负责修改箭头的方向,代码如下

        //随着节点位置的改变动态改变箭头
        Raphael.fn.drawArr = function (obj) {
            var point = getStartEnd(obj.obj1, obj.obj2);
            var path1 = getArr(point.start.x, point.start.y, point.end.x, point.end.y, 8);
            if (obj.arrPath) {
                obj.arrPath.attr({ path: path1 });
            } else {
                obj.arrPath = this.path(path1);
            }
            return obj;
        };
 

首先需要确定箭头的起始位置,

point包含两个点,

point.start为起点,

point.end为终点,

然后需要确定箭头的绘图路径

一个箭头包含三个线段,四个点

1:起点,2:终点,3:箭头终点1,4:箭头终点2

image

在此函数中,判断如果箭头已经被绘制过,

只要修改属性即可

如果没有被绘制过,则需要重新绘制

 

下面来看一下动态确定起点和终点的代码

function getStartEnd(obj1, obj2) {
            var bb1 = obj1.getBBox(),
                bb2 = obj2.getBBox();
            var p = [
                    { x: bb1.x + bb1.width / 2, y: bb1.y - 1 },
                    { x: bb1.x + bb1.width / 2, y: bb1.y + bb1.height + 1 },
                    { x: bb1.x - 1, y: bb1.y + bb1.height / 2 },
                    { x: bb1.x + bb1.width + 1, y: bb1.y + bb1.height / 2 },
                    { x: bb2.x + bb2.width / 2, y: bb2.y - 1 },
                    { x: bb2.x + bb2.width / 2, y: bb2.y + bb2.height + 1 },
                    { x: bb2.x - 1, y: bb2.y + bb2.height / 2 },
                    { x: bb2.x + bb2.width + 1, y: bb2.y + bb2.height / 2 }
                ];
            var d = {}, dis = [];
            for (var i = 0; i < 4; i++) {
                for (var j = 4; j < 8; j++) {
                    var dx = Math.abs(p[i].x - p[j].x),
                        dy = Math.abs(p[i].y - p[j].y);
                    if (
                         (i == j - 4) ||
                         (((i != 3 && j != 6) || p[i].x < p[j].x) &&
                         ((i != 2 && j != 7) || p[i].x > p[j].x) &&
                         ((i != 0 && j != 5) || p[i].y > p[j].y) &&
                         ((i != 1 && j != 4) || p[i].y < p[j].y))
                       ) {
                        dis.push(dx + dy);
                        d[dis[dis.length - 1]] = [i, j];
                    }
                }
            }
            if (dis.length == 0) {
                var res = [0, 4];
            } else {
                res = d[Math.min.apply(Math, dis)];
            }
            var result = {};
            result.start = {};
            result.end = {};
            result.start.x = p[res[0]].x;
            result.start.y = p[res[0]].y;
            result.end.x = p[res[1]].x;
            result.end.y = p[res[1]].y;
            return result;
        }
 

这段代码来自Raphael官方demo

不是我写的

也一时半会说不清楚,

大家还是自己去研究吧

 

确定箭头路径的代码如下

        //获取组成箭头的三条线段的路径
        function getArr(x1, y1, x2, y2, size) {
            var angle = Raphael.angle(x1, y1, x2, y2);//得到两点之间的角度
            var a45 = Raphael.rad(angle - 45);//角度转换成弧度
            var a45m = Raphael.rad(angle + 45);
            var x2a = x2 + Math.cos(a45) * size;
            var y2a = y2 + Math.sin(a45) * size;
            var x2b = x2 + Math.cos(a45m) * size;
            var y2b = y2 + Math.sin(a45m) * size;
            var result = ["M", x1, y1, "L", x2, y2, "L", x2a, y2a, "M", x2, y2, "L", x2b, y2b];
            return result;
        }
 

此函数把箭头路径作为数组反馈给调用函数

数组中

M表示画笔起点移动到此点

L表示从某点绘制到某点,绘制直线

以上函数反馈结果的意思是:

画笔从(x1,y1)开始绘制直线到(x2,y2),然后从(x2,y2)绘制直线到(x2a,y2a)然后画笔移动到(x2,y2)然后从(x2,y2)绘制直线到(x2b,y2b)

在确定这几个点的过程中

用到了一些数学知识,具体原理也不多说了

 

 

喜欢的朋友请点支持!谢谢大家!

目录
相关文章
|
2月前
|
存储 人工智能 前端开发
前端大模型应用笔记(三):Vue3+Antdv+transformers+本地模型实现浏览器端侧增强搜索
本文介绍了一个纯前端实现的增强列表搜索应用,通过使用Transformer模型,实现了更智能的搜索功能,如使用“番茄”可以搜索到“西红柿”。项目基于Vue3和Ant Design Vue,使用了Xenova的bge-base-zh-v1.5模型。文章详细介绍了从环境搭建、数据准备到具体实现的全过程,并展示了实际效果和待改进点。
160 2
|
19天前
|
缓存 JavaScript 前端开发
JavaScript 与 DOM 交互的基础及进阶技巧,涵盖 DOM 获取、修改、创建、删除元素的方法,事件处理,性能优化及与其他前端技术的结合,助你构建动态交互的网页应用
本文深入讲解了 JavaScript 与 DOM 交互的基础及进阶技巧,涵盖 DOM 获取、修改、创建、删除元素的方法,事件处理,性能优化及与其他前端技术的结合,助你构建动态交互的网页应用。
28 5
|
1月前
|
前端开发 JavaScript API
前端开发的秘密花园:这些技巧让你轻松应对各种浏览器兼容性问题!
【10月更文挑战第31天】前端开发是一个充满创意与挑战的领域,追求极致用户体验的同时,浏览器兼容性问题却时常阻碍我们前进。本文将介绍几种解决浏览器兼容性的最佳实践:使用CSS前缀、Autoprefixer工具、现代JavaScript特性与Babel转译、Polyfill与Feature Detection、响应式设计以及跨域问题处理。掌握这些技巧,助你轻松应对各种兼容性难题,创建更稳定、用户友好的网页应用。
30 3
|
1月前
|
机器学习/深度学习 自然语言处理 前端开发
前端神经网络入门:Brain.js - 详细介绍和对比不同的实现 - CNN、RNN、DNN、FFNN -无需准备环境打开浏览器即可测试运行-支持WebGPU加速
本文介绍了如何使用 JavaScript 神经网络库 **Brain.js** 实现不同类型的神经网络,包括前馈神经网络(FFNN)、深度神经网络(DNN)和循环神经网络(RNN)。通过简单的示例和代码,帮助前端开发者快速入门并理解神经网络的基本概念。文章还对比了各类神经网络的特点和适用场景,并简要介绍了卷积神经网络(CNN)的替代方案。
|
1月前
|
缓存 前端开发 JavaScript
"面试通关秘籍:深度解析浏览器面试必考问题,从重绘回流到事件委托,让你一举拿下前端 Offer!"
【10月更文挑战第23天】在前端开发面试中,浏览器相关知识是必考内容。本文总结了四个常见问题:浏览器渲染机制、重绘与回流、性能优化及事件委托。通过具体示例和对比分析,帮助求职者更好地理解和准备面试。掌握这些知识点,有助于提升面试表现和实际工作能力。
65 1
|
2月前
|
机器学习/深度学习 自然语言处理 前端开发
前端大模型入门:Transformer.js 和 Xenova-引领浏览器端的机器学习变革
除了调用API接口使用Transformer技术,你是否想过在浏览器中运行大模型?Xenova团队推出的Transformer.js,基于JavaScript,让开发者能在浏览器中本地加载和执行预训练模型,无需依赖服务器。该库利用WebAssembly和WebGPU技术,大幅提升性能,尤其适合隐私保护、离线应用和低延迟交互场景。无论是NLP任务还是实时文本生成,Transformer.js都提供了强大支持,成为构建浏览器AI应用的核心工具。
553 1
|
2月前
|
NoSQL 前端开发 MongoDB
前端的全栈之路Meteor篇(三):运行在浏览器端的NoSQL数据库副本-MiniMongo介绍及其前后端数据实时同步示例
MiniMongo 是 Meteor 框架中的客户端数据库组件,模拟了 MongoDB 的核心功能,允许前端开发者使用类似 MongoDB 的 API 进行数据操作。通过 Meteor 的数据同步机制,MiniMongo 与服务器端的 MongoDB 实现实时数据同步,确保数据一致性,支持发布/订阅模型和响应式数据源,适用于实时聊天、项目管理和协作工具等应用场景。
|
2月前
|
JavaScript API
深入解析JS中的visibilitychange事件:监听浏览器标签间切换的利器
深入解析JS中的visibilitychange事件:监听浏览器标签间切换的利器
139 0
|
4月前
|
机器学习/深度学习 存储 前端开发
实战揭秘:如何借助TensorFlow.js的强大力量,轻松将高效能的机器学习模型无缝集成到Web浏览器中,从而打造智能化的前端应用并优化用户体验
【8月更文挑战第31天】将机器学习模型集成到Web应用中,可让用户在浏览器内体验智能化功能。TensorFlow.js作为在客户端浏览器中运行的库,提供了强大支持。本文通过问答形式详细介绍如何使用TensorFlow.js将机器学习模型带入Web浏览器,并通过具体示例代码展示最佳实践。首先,需在HTML文件中引入TensorFlow.js库;接着,可通过加载预训练模型如MobileNet实现图像分类;然后,编写代码处理图像识别并显示结果;此外,还介绍了如何训练自定义模型及优化模型性能的方法,包括模型量化、剪枝和压缩等。
58 1
|
4月前
|
编解码 JavaScript 前端开发
JS逆向浏览器脱环境专题:事件学习和编写、DOM和BOM结构、指纹验证排查、代理自吐环境通杀环境检测、脱环境框架、脱环境插件解决
JS逆向浏览器脱环境专题:事件学习和编写、DOM和BOM结构、指纹验证排查、代理自吐环境通杀环境检测、脱环境框架、脱环境插件解决
128 1