案例菜单(案例菜单组件为自定义测试组件,有多余代码,仅供参考)
功能点如下:
1. 右键展开菜单,点击请求节点数据,或展开下级菜单
2. 拖动节点或画布,菜单跟随节点
3. 缩放画布,菜单同步缩放
4. 对其它或者空白区域进行操作时,关闭菜单
主要流程:
监听右键-》获取鼠标点击节点所在位置-》将给位置赋值给菜单
步骤1: network.on("oncontext", (e) => {})
其中步骤2获取节点位置可通过vis的api获取
代码如下
- <template>
- <div>
- <div id="mynetwork" class="mynetwork2" :style="{ width: width, height: height }"></div>
- <CustomMenu
- v-if="menuShow"
- :menuTop="menuTop"
- :menuLeft="menuLeft"
- :menuScale="menuScale"
- :menuShow="menuShow"
- :nodeType="nodeType"
- @closeMenu="hideCircle"
- />
- </div>
- </template>
-
- <script>
- // const vis = require("vis-network/dist/vis-network.min.js");
- import vis from "vis";
- // import _ from "lodash";
- import CustomMenu from "./customMenu.vue";
-
- var network = null;
- let menuTimer = null; //右键菜单
- export default {
- components: { CustomMenu },
- computed: {
- styleFun: function() {
- console.log(this.menuScale, this.menuLeft, this.menuTop, "menuScale");
- var str = `left: ${this.menuLeft}; top: ${this.menuTop};transform:scale(${this.menuScale})`;
- return str;
- },
- },
- props: {
- width: {
- type: String,
- default: "100%",
- },
- height: {
- type: String,
- default: "100%",
- },
- },
- data() {
- return {
- menuShow: false, //自定义右键菜单
- menuLeft: 0,
- menuTop: 0,
- menuScale: 0,
- nodeType: null,
- selectedNode: null, //选中节点
- // 节点
- nodes: new vis.DataSet([
- {
- id: 1,
- label: "Node 1",
- image: {
- selected: require("@/assets/people-opened.png"),
- unselected: require("@/assets/people-def.png"),
- },
- },
- {
- id: 2,
- label: "Node 2",
- image: {
- selected: require("@/assets/people-opened.png"),
- unselected: require("@/assets/people-def.png"),
- },
- },
-
- {
- id: 3,
- label: "Node 3",
- image: {
- selected: require("@/assets/people-opened.png"),
- unselected: require("@/assets/people-def.png"),
- },
- },
-
- {
- id: 4,
- label: "Node 4",
- image: {
- selected: require("@/assets/people-opened.png"),
- unselected: require("@/assets/people-def.png"),
- },
- },
-
- {
- id: 5,
- label: "Node 5",
- title: "5",
- image: {
- selected: require("@/assets/people-opened.png"),
- unselected: require("@/assets/people-def.png"),
- },
- },
- ]),
- // 边
- edges: new vis.DataSet([
- { id: 1, from: 1, to: 3 },
- { id: 2, from: 1, to: 3 },
- { id: 5, from: 3, to: 1 },
- { from: 1, to: 2, title: "1-2", label: "1-2" },
- { from: 2, to: 4 },
- { from: 2, to: 5 },
- // { from: 3, to: 3 },
- ]),
- };
- },
- mounted() {
- this.init();
- },
- methods: {
- init() {
- // create a network
- var container = document.getElementById("mynetwork");
- var data = {
- nodes: this.nodes,
- edges: this.edges,
- };
-
- var options = {
- interaction: {
- dragNodes: true,
- // dragView: true,
- hideEdgesOnDrag: false,
- // hideEdgesOnZoom: false,
- hideNodesOnDrag: false,
- hover: true,
- hoverConnectedEdges: true,
- keyboard: {
- enabled: false,
- speed: { x: 10, y: 10, zoom: 0.02 },
- bindToWindow: true, //设置为false,确保键盘快捷键仅在具有焦点的关系图实例上工作
- },
- multiselect: true, //长时间单击(或触摸)以及控件单击将添加到’选择中‘。
- navigationButtons: false, //如果为true,则在关系图canvas上绘制导航按钮。这些按钮是HTML,可以使用CSS自定义样式。
- selectable: true, //用户可以选择节点和边。
- selectConnectedEdges: true, //在选中节点时,与其连接的边将高亮(高亮色)显示
- tooltipDelay: 300, //提示框显示的延迟时间(毫秒)
- zoomView: true,
- },
-
- layout: {
- randomSeed: 1, //配置每次生成的节点位置都一样,参数为数字1、2等
- },
- nodes: {
- // shape: "circularImage",
- shape: "image",
- // opacity:0.1,
- // borderWidth: 0,
- size: 23,
- color: {
- // border: "rgba(255, 255, 255, 1)",
- background: "rgba(102, 51, 255, 0)",
- },
- font: {
- color: "#fff",
- },
- shadow: {
- enabled: true,
- // color: "rgba(255, 215, 0, 0.8)",
- color: "rgba(153, 102, 204, 0.8)",
- size: 40,
- x: 0,
- y: 0,
- },
- chosen: {
- label: false,
- // node: function(values, id, selected, hovering) {
- node: function(values) {
- values.shadowColor = "rgba(255, 215, 0, 0.8)";
- },
- },
- },
- edges: {
- hoverWidth: 0,
- shadow: true,
- arrows: {
- to: {
- enabled: true,
- type: "arrow",
- },
- },
- color: {
- color: "#525A81",
- highlight: "#FFBA30",
- hover: "#FFBA30",
- // inherit: "from",
- opacity: 1.0,
- },
- font: {
- color: "#fff",
- size: 12, // px
- strokeWidth: 0, // px
- },
- },
- };
- network = new vis.Network(container, data, options);
- // 事件监听
- this.networkEvent();
- },
- // 拓扑网络监听事件
- networkEvent() {
- const _this = this;
- //单击
- network.on("click", (e) => {
- console.log("单击:", e);
- this.hideCircle();
- });
- // 单击鼠标右键
- network.on("oncontext", (e) => {
- console.log(e);
- // 获取节点 id
- var nodeId = network.getNodeAt(e.pointer.DOM);
- console.log(nodeId, this.nodes.get(nodeId));
- console.log("单击:", network.getPositions(nodeId));
-
- e.event.preventDefault();
- if (_this.menuShow) {
- this.hideCircle();
- setTimeout(() => {
- if (nodeId) {
- this.selectedNode = nodeId;
- this.changeMenuPosition(e);
- } else {
- this.hideCircle();
- }
- }, 0);
- } else {
- if (nodeId) {
- this.selectedNode = nodeId;
- this.changeMenuPosition(e);
- } else {
- this.hideCircle();
- }
- }
- });
-
- // 鼠标缩放事件
- network.on("zoom", (e) => {
- console.log(e);
- // this.$emit("changeZoom", e.scale);
- });
- },
- getNodePosition() {
- // console.log(e);
- var nodeId = this.selectedNode;
- // 通过节点id 获取节点中心点的在画布的位置
- let nodePosition = network.getPositions(nodeId);
- // console.log(nodePosition, "nodePosition");
- // 坐标转化
- nodePosition = network.canvasToDOM(nodePosition[nodeId]);
- return nodePosition;
- },
- // 设置环形位置
- setCirclePostion(x, y, scale) {
- this.menuShow = true;
- this.menuLeft = Math.floor(x) + "px";
- this.menuTop = Math.floor(y) + "px";
- this.menuScale = scale;
- },
- // 更新右键菜单位置
- changeMenuPosition(e) {
- menuTimer = setInterval(() => {
- // 获取缩放尺寸
- let scale = network.getScale();
- let nodePosition = this.getNodePosition(e);
- // console.log("nodePosition:", nodePosition);
- this.setCirclePostion(
- /*
- *转换的坐标为相对容器DOM的坐标
- *82菜单半径
- */
- nodePosition.x - 82,
- nodePosition.y - 82,
- scale
- );
- }, 10);
- },
- // 隐藏圆环选项
- hideCircle(status) {
- window.clearInterval(menuTimer);
- // clearInterval(menuTimer);
- this.menuShow = false;
- if (status) {
- //圆环菜单单击隐藏,修改该节点和连接边的展开参数
- let edges = [];
- this.nodes.updateOnly({
- id: this.selectedNode,
- open: true,
- // changEdge: e.edges[0]
- });
- edges = network.getConnectedEdges(this.selectedNode);
-
- if (edges.length > 0) {
- edges.forEach((item) => {
- this.edges.updateOnly({
- id: item,
- open: true,
- // length: 600
- });
- });
- }
-
- var option = {
- scale: 1.0,
- offset: { x: 0, y: 0 },
- animation: {
- duration: 1000,
- easingFunction: "easeInOutQuad",
- },
- };
-
- network.focus(this.selectedNode, option);
- }
- },
- },
- beforeDestroy() {
- window.clearInterval(menuTimer);
- },
- };
- </script>
-
- <style lang="less" scoped>
- .box,
- .mynetwork2 {
- height: 100%;
- background-color: #2a004a;
- }
-
- .cricle {
- position: absolute;
- left: 0px;
- top: 0px;
- width: 100px;
- height: 100px;
- border: 30px solid rgba(200, 200, 200, 0.7);
- border-radius: 50px;
- box-sizing: border-box;
- z-index: 10;
- // display: none;
-
- span {
- position: absolute;
- left: 0;
- right: 0;
- width: 30px;
- height: 20px;
- line-height: 20px;
- text-align: center;
- font-size: 10px;
- cursor: pointer;
- &:hover {
- color: #1b68ff;
- }
-
- &:nth-child(1) {
- left: 5px;
- top: -25px;
- }
-
- &:nth-child(2) {
- left: 37px;
- top: -5px;
- }
-
- &:nth-child(3) {
- left: 37px;
- top: 25px;
- }
-
- &:nth-child(4) {
- left: 5px;
- top: 44px;
- }
-
- &:nth-child(5) {
- left: -27px;
- top: 25px;
- }
-
- &:nth-child(6) {
- left: -27px;
- top: -5px;
- }
- }
- }
- </style>
菜单代码如下
- <template>
- <div id="ring-option" class="custom-menu" :style="styleFun" @contextmenu.prevent.capture>
-
- <div class="inner-ring">
- <div
- :class="['innerWrapper', innerWrapperClass[index]]"
- v-for="(item, index) in optionData"
- :key="index"
- >
- <div
- :class="[
- 'innerRingOption',
- innerOptionClass[index],
- startRun ? 'start-animation' + (index + 1) : '',
- ]"
- >
-
- div>
- div>
- div>
-
-
-
- <div class="out-ring" @click="cl('ccc', 2)">
- <div
- :class="['outWrapper', 'outWrapper' + index]"
- v-for="(item, index) in outOptionData"
- :key="index"
- >
- <div
- @click="cl(index, 1)"
- :class="['outRingOption', 'outOption' + index, classObject + (index + 1)]"
- >
- <div class="out-option-bg" @click="cl(index, 2)">div>
- div>
- div>
- div>
-
-
- <div class="inner-option-list" v-if="showOptionListInner">
- <div
- :class="['option-item', { ashFont: startOut == 1 }]"
- v-for="(item, index) in optionData"
- :key="index"
- @click.stop="close(index + 1, true)"
- >
- {{ item.name }}
- div>
- div>
-
-
-
- <div class="out-option-list" v-if="showOptionListOut" @click="cl('aa', 2)">
- <div
- class="option-item"
- v-for="(item, index) in outOptionData"
- :key="index"
- @click.stop="getExtraList(item.key)"
- >
- {{ item.value }}
- div>
- div>
- div>
- template>
-
- <script>
- // import Bus from "bus";
-
- // import { getExtra } from "@/api/api.js";
-
- export default {
- props: {
- menuTop: String,
- menuLeft: String,
- menuScale: Number,
- menuShow: Boolean,
- nodeType: String,
- delayTime: Number,
- },
- data() {
- return {
- startRun: false,
- // showDefaultOption: true,
- showOptionListInner: false,
- optionTimeInner: null,
- innerWrapperClass: ["left-top", "right-top", "right-bottom", "left-bottom"],
- innerOptionClass: [
- "left-top-ring",
- "right-top-ring",
- "right-bottom-ring",
- "left-bottom-ring",
- ],
- optionData: [{ name: "一度" }, { name: "二度" }, { name: "三度" }, { name: "定向扩展" }],
- // 外圈
- startOut: 0, //3:不动,1:展开;2收回
- optionTimeOut: null,
- outOptionData: [
- // { value: "同行" },
- // { value: "同航班" },
- // { value: "同酒店" },
- // { value: "同网吧" },
- // { value: "同火车" }
- ],
- showOptionListOut: false,
- hasClickOutOption: false, //默认只有点击'选项'才有交互,点击圆盘不交互
- };
- },
- computed: {
- styleFun: function() {
- console.log(this.menuScale, this.menuLeft, this.menuTop, "menuScale");
- var str = `left: ${this.menuLeft}; top: ${this.menuTop};transform:scale(${this.menuScale})`;
- return str;
- },
- scaleFun: function() {
- console.log(this.menuScale);
- var scale = this.menuScale;
- return `transform:scale(${scale})`;
- },
- classObject: function() {
- let classStr = "";
- console.log("this.startOut:", this.startOut);
- if (this.startOut == 1) {
- classStr = "start-out";
- } else if (this.startOut == 2) {
- classStr = "end-out";
- }
- return classStr;
- },
- },
- watch: {
- menuLeft() {
- // console.log(n);
- // this.startRun = false;
- this.runAnimation();
- },
- },
- mounted() {
- this.runAnimation();
- clearTimeout(this.optionTime);
- this.optionTime = setTimeout(() => {
- this.showOptionListInner = true;
- // this.showDefaultOption = false;
- // console.log("this.showOptionListInner:", this.showOptionListInner);
- }, 1000);
- },
- methods: {
- runAnimation() {
- // const ringDom = document.querySelectorAll(".innerRingOption");
- let delayTime = this.delayTime || 500;
- setTimeout(() => {
- this.startRun = true;
- }, delayTime);
- },
- close(e, n) {
- console.log("++++++", e);
- if (n) {
- this.hasClickOutOption = n;
- }
- if (e < 4) {
- this.$bus.emit("busGetNodes", e);
- this.$emit("closeMenu", true);
- // } else if (e == 4 && this.nodeType == "person") {
- } else if (e == 4) {
- console.log("21:", this.startOut);
- clearTimeout(this.optionTimeOut);
- if (this.startOut == 0 || this.startOut == 2) {
- this.getExtraLabel(); //获取外圈数据
- } else if (this.startOut == 1) {
- this.startOut = 2; //关闭外圈
- this.showOptionListOut = false; //隐藏外圈字
- }
- console.log("22:", this.startOut);
- }
- },
- getExtraList(e) {
- this.startOut = 0; //关闭外圈
- this.showOptionListOut = false; //隐藏外圈字
- this.$emit("closeMenu", true);
- this.$bus.emit("busGetExtraNodes", e);
- console.log("----", e);
- },
- cl(e, d) {
- // 测试
- // 通过判断点击的位置和各区块的位置界限判断点击事件
- // 或者 点击字 执行操作,点击圆环其它地方,关闭圆环
- console.log("----", e, d);
- },
- getExtraLabel() {
- // getExtra(this.nodeType).then(res => {
- // this.outOptionData = res;
- // console.log(res, this.outOptionData);
- // if (this.outOptionData.length > 0) {
- // this.startOut = 1; //展开外圈
- // this.optionTimeOut = setTimeout(() => {
- // this.showOptionListOut = true; //显示外圈字
- // }, 1000);
- // }
- // });
- },
- },
- destroyed() {
- clearTimeout(this.optionTime);
- },
- };
- script>
-
- <style lang="less" scoped>
- @bordercolor: rgba(69, 77, 116);
-
- @keyframes rorateOption {
- 0% {
- opacity: 0;
- }
-
- 100% {
- opacity: 1;
- }
- }
-
- @keyframes rorateOption1 {
- 0% {
- transform: rotate(-90deg);
- opacity: 0;
- }
-
- 100% {
- transform: rotate(0deg);
- opacity: 1;
- }
- }
-
- @keyframes rorateOption2 {
- 0% {
- transform: rotate(-180deg);
- opacity: 0;
- }
-
- 100% {
- transform: rotate(0deg);
- opacity: 1;
- }
- }
-
- @keyframes rorateOption3 {
- 0% {
- transform: rotate(-270deg);
- opacity: 0;
- }
-
- 100% {
- transform: rotate(0deg);
- opacity: 1;
- }
- }
-
- .custom-menu {
- width: 164px;
- height: 164px;
- position: absolute;
- // 内圈
- .innerWrapper {
- width: 82px;
- height: 82px;
- position: absolute;
- // overflow: hidden;
-
- .innerRingOption {
- //82px为圆环
- // width: 82px;
- // height: 82px;
- width: 164px;
- height: 164px;
- // border: 20px solid #45b7ff;
- border-style: solid;
- border-width: 41px;
- border-color: transparent;
- border-radius: 50%;
- position: absolute;
- opacity: 0;
- transition-duration: 0.3s;
- // &:hover {
- // border-width: 50px;
- // }
- }
-
- .left-top-ring {
- border-top-color: @bordercolor;
-
- .option-item {
- left: 27px;
- top: -27px;
- }
- }
- .right-top-ring {
- border-right-color: @bordercolor;
- transform: rotate(-90deg);
- right: 0;
-
- .option-item {
- left: 93px;
- top: 22px;
- }
- }
-
- .right-bottom-ring {
- border-bottom-color: @bordercolor;
- transform: rotate(-180deg);
- right: 0;
- bottom: 0;
-
- .option-item {
- left: 27px;
- top: 90px;
- }
- }
-
- .left-bottom-ring {
- border-left-color: @bordercolor;
- transform: rotate(-270deg);
- bottom: 0;
-
- .option-item {
- left: -32px;
- top: 21px;
- width: 30px;
- }
- }
-
- .option-item {
- position: absolute;
- color: #fff;
- cursor: pointer;
- }
-
- // .option-item:hover {
- // // color: @bordercolor;
- // color: #45b7ff;
- // font-weight: bold;
- // // font-size: 18px;
- // }
-
- .start-animation1 {
- animation-name: rorateOption;
- animation-duration: 0.3s;
- animation-fill-mode: forwards;
- }
-
- .start-animation2 {
- animation-name: rorateOption1;
- animation-duration: 0.3s;
- animation-fill-mode: forwards;
- }
-
- .start-animation3 {
- animation-name: rorateOption2;
- animation-duration: 0.3s;
- animation-fill-mode: forwards;
- }
-
- .start-animation4 {
- animation-name: rorateOption3;
- animation-duration: 0.3s;
- animation-fill-mode: forwards;
- }
- }
- .left-top {
- left: 0;
- top: 0;
- }
-
- .right-top {
- // left: 82px;
- right: 0;
- top: 0;
- }
-
- .right-bottom {
- // left: 82px;
- // top: 82px;
- right: 0;
- bottom: 0;
- }
-
- .left-bottom {
- left: 0;
- bottom: 0;
- // top: 82px;
- }
-
- .inner-option-list {
- position: relative;
- width: 164px;
- height: 164px;
-
- .option-item {
- position: absolute;
- cursor: pointer;
- color: #fff;
- // font-size: 14px;
- &:first-child {
- left: 68px;
- top: 14px;
- }
-
- &:nth-child(2) {
- left: 129px;
- top: 63px;
- width: 22px;
- }
-
- &:nth-child(3) {
- left: 68px;
- top: 131px;
- }
-
- &:nth-child(4) {
- left: 9px;
- top: 62px;
- width: 30px;
- }
- }
-
- .option-item:hover {
- color: #6b05e8;
- // font-weight: bold;
- // font-size: 18px;
- }
-
- .ashFont {
- color: #969ecf;
- }
- }
-
- // 打开
- @keyframes optionOutStart1 {
- 0% {
- // transform: rotate(0deg) skewX(18deg);
- opacity: 0;
- }
-
- 100% {
- // transform: rotate(72deg) skewX(20deg);
- opacity: 1;
- }
- }
-
- @keyframes optionOutStart2 {
- 0% {
- transform: rotate(0deg) skewX(20deg);
- opacity: 0;
- }
-
- 100% {
- transform: rotate(72deg) skewX(20deg);
- opacity: 1;
- }
- }
-
- @keyframes optionOutStart3 {
- 0% {
- transform: rotate(0deg) skewX(20deg);
- opacity: 0;
- }
-
- 100% {
- transform: rotate(144deg) skewX(20deg);
- opacity: 1;
- }
- }
-
- @keyframes optionOutStart4 {
- 0% {
- transform: rotate(0deg) skewX(20deg);
- opacity: 0;
- }
-
- 100% {
- transform: rotate(216deg) skewX(20deg);
- opacity: 1;
- }
- }
-
- @keyframes optionOutStart5 {
- 0% {
- transform: rotate(0deg) skewX(20deg);
- opacity: 0;
- }
-
- 100% {
- transform: rotate(288deg) skewX(20deg);
- opacity: 1;
- }
- }
-
- // 关闭
- @keyframes optionOutClose1 {
- 0% {
- // transform: rotate(0deg) skewX(20deg);
- opacity: 1;
- }
-
- 100% {
- // transform: rotate(0deg) skewX(20deg);
- opacity: 0;
- }
- }
-
- @keyframes optionOutClose2 {
- 0% {
- transform: rotate(72deg) skewX(20deg);
- opacity: 1;
- }
-
- 100% {
- transform: rotate(0deg) skewX(20deg);
- opacity: 0;
- }
- }
-
- @keyframes optionOutClose3 {
- 0% {
- transform: rotate(144deg) skewX(20deg);
- opacity: 1;
- }
-
- 100% {
- transform: rotate(0deg) skewX(20deg);
- opacity: 0;
- }
- }
-
- @keyframes optionOutClose4 {
- 0% {
- transform: rotate(216deg) skewX(20deg);
- opacity: 1;
- }
-
- 100% {
- transform: rotate(0deg) skewX(20deg);
- opacity: 0;
- }
- }
-
- @keyframes optionOutClose5 {
- 0% {
- transform: rotate(288deg) skewX(20deg);
- opacity: 1;
- }
-
- 100% {
- transform: rotate(0deg) skewX(20deg);
- opacity: 0;
- }
- }
-
- // 外圈
- .outWrapper {
- width: 246px;
- height: 246px;
- position: absolute;
- left: -41px;
- top: -41px;
- overflow: hidden;
- border-radius: 50%;
- .outRingOption {
- width: 246px;
- height: 246px;
- position: absolute;
- transition-duration: 1s;
- // border: 1px solid #fff;
- overflow: hidden;
- transform: rotate(0deg) skewX(20deg);
- transform-origin: 246px 246px;
- top: -123px;
- left: -123px;
- opacity: 0;
- // pointer-events: auto;
- animation-duration: 1s;
- animation-fill-mode: forwards;
- }
- .out-option-bg {
- width: 246px;
- height: 246px;
- position: absolute;
- border: 41px solid;
- border-radius: 50%;
- // border-color: #464e75a3;
- border-color: #454d74;
- right: -123px;
- bottom: -123px;
- transform: skewX(-18deg);
- // pointer-events: auto;
- }
-
- // .out-option-bg:hover {
- // border-color: #ff0000;
- // }
-
- // 打开
- .start-out1 {
- animation-name: optionOutStart1;
- }
-
- .start-out2 {
- animation-name: optionOutStart2;
- }
- .start-out3 {
- animation-name: optionOutStart3;
- }
- .start-out4 {
- animation-name: optionOutStart4;
- }
- .start-out5 {
- animation-name: optionOutStart5;
- }
-
- // 关闭
- .end-out1 {
- animation-name: optionOutClose1;
- }
-
- .end-out2 {
- animation-name: optionOutClose2;
- }
-
- .end-out3 {
- animation-name: optionOutClose3;
- }
-
- .end-out4 {
- animation-name: optionOutClose4;
- }
-
- .end-out5 {
- animation-name: optionOutClose5;
- }
- }
-
- .out-option-list {
- position: absolute;
- width: 246px;
- height: 246px;
- left: -41px;
- top: -41px;
- // pointer-events: none;
- .option-item {
- position: absolute;
- cursor: pointer;
- color: #fff;
- // pointer-events: none;
- &:first-child {
- left: 25px;
- top: 56px;
- transform: rotate(-56deg);
- }
-
- &:nth-child(2) {
- left: 138px;
- top: 18px;
- /* width: 22px; */
- transform: rotate(20deg);
- }
-
- &:nth-child(3) {
- left: 212px;
- top: 94px;
- width: 22px;
- }
- &:nth-child(4) {
- left: 138px;
- top: 212px;
- /* width: 30px; */
- transform: rotate(-14deg);
- }
-
- &:nth-child(5) {
- left: 24px;
- top: 177px;
- /* width: 30px; */
- transform: rotate(50deg);
- }
- }
-
- .option-item:hover {
- color: #6b05e8;
- // font-weight: bold;
- // font-size: 18px;
- }
- }
- }
- style>
效果如下

存在问题
1. dom元素展开的菜单会遮盖节点
2. 遮盖超过实际展示的部分
解决:用canvas绘画菜单(解决2),在initRedraw、beforeDrawing、afterDrawing触发事件中执行绘画