• vis 右键节点展开菜单


    案例菜单(案例菜单组件为自定义测试组件,有多余代码,仅供参考)

    功能点如下:

    1. 右键展开菜单,点击请求节点数据,或展开下级菜单

    2. 拖动节点或画布,菜单跟随节点

    3. 缩放画布,菜单同步缩放

    4. 对其它或者空白区域进行操作时,关闭菜单

    主要流程:

    监听右键-》获取鼠标点击节点所在位置-》将给位置赋值给菜单

    步骤1: network.on("oncontext", (e) => {})

    其中步骤2获取节点位置可通过vis的api获取

    1. 获取鼠标点击处画布中的位置,e.pointer.DOM
    2. 根据在画布中的点击位置返回节点id,nodeId = network.getNodeAt(e.pointer.DOM)
    3. 通过节点id获取节点中心点在画布中的位置,network.getPositions(nodeId),返回的是对象

    代码如下

    1. <template>
    2. <div>
    3. <div id="mynetwork" class="mynetwork2" :style="{ width: width, height: height }"></div>
    4. <CustomMenu
    5. v-if="menuShow"
    6. :menuTop="menuTop"
    7. :menuLeft="menuLeft"
    8. :menuScale="menuScale"
    9. :menuShow="menuShow"
    10. :nodeType="nodeType"
    11. @closeMenu="hideCircle"
    12. />
    13. </div>
    14. </template>
    15. <script>
    16. // const vis = require("vis-network/dist/vis-network.min.js");
    17. import vis from "vis";
    18. // import _ from "lodash";
    19. import CustomMenu from "./customMenu.vue";
    20. var network = null;
    21. let menuTimer = null; //右键菜单
    22. export default {
    23. components: { CustomMenu },
    24. computed: {
    25. styleFun: function() {
    26. console.log(this.menuScale, this.menuLeft, this.menuTop, "menuScale");
    27. var str = `left: ${this.menuLeft}; top: ${this.menuTop};transform:scale(${this.menuScale})`;
    28. return str;
    29. },
    30. },
    31. props: {
    32. width: {
    33. type: String,
    34. default: "100%",
    35. },
    36. height: {
    37. type: String,
    38. default: "100%",
    39. },
    40. },
    41. data() {
    42. return {
    43. menuShow: false, //自定义右键菜单
    44. menuLeft: 0,
    45. menuTop: 0,
    46. menuScale: 0,
    47. nodeType: null,
    48. selectedNode: null, //选中节点
    49. // 节点
    50. nodes: new vis.DataSet([
    51. {
    52. id: 1,
    53. label: "Node 1",
    54. image: {
    55. selected: require("@/assets/people-opened.png"),
    56. unselected: require("@/assets/people-def.png"),
    57. },
    58. },
    59. {
    60. id: 2,
    61. label: "Node 2",
    62. image: {
    63. selected: require("@/assets/people-opened.png"),
    64. unselected: require("@/assets/people-def.png"),
    65. },
    66. },
    67. {
    68. id: 3,
    69. label: "Node 3",
    70. image: {
    71. selected: require("@/assets/people-opened.png"),
    72. unselected: require("@/assets/people-def.png"),
    73. },
    74. },
    75. {
    76. id: 4,
    77. label: "Node 4",
    78. image: {
    79. selected: require("@/assets/people-opened.png"),
    80. unselected: require("@/assets/people-def.png"),
    81. },
    82. },
    83. {
    84. id: 5,
    85. label: "Node 5",
    86. title: "5",
    87. image: {
    88. selected: require("@/assets/people-opened.png"),
    89. unselected: require("@/assets/people-def.png"),
    90. },
    91. },
    92. ]),
    93. //
    94. edges: new vis.DataSet([
    95. { id: 1, from: 1, to: 3 },
    96. { id: 2, from: 1, to: 3 },
    97. { id: 5, from: 3, to: 1 },
    98. { from: 1, to: 2, title: "1-2", label: "1-2" },
    99. { from: 2, to: 4 },
    100. { from: 2, to: 5 },
    101. // { from: 3, to: 3 },
    102. ]),
    103. };
    104. },
    105. mounted() {
    106. this.init();
    107. },
    108. methods: {
    109. init() {
    110. // create a network
    111. var container = document.getElementById("mynetwork");
    112. var data = {
    113. nodes: this.nodes,
    114. edges: this.edges,
    115. };
    116. var options = {
    117. interaction: {
    118. dragNodes: true,
    119. // dragView: true,
    120. hideEdgesOnDrag: false,
    121. // hideEdgesOnZoom: false,
    122. hideNodesOnDrag: false,
    123. hover: true,
    124. hoverConnectedEdges: true,
    125. keyboard: {
    126. enabled: false,
    127. speed: { x: 10, y: 10, zoom: 0.02 },
    128. bindToWindow: true, //设置为false,确保键盘快捷键仅在具有焦点的关系图实例上工作
    129. },
    130. multiselect: true, //长时间单击(或触摸)以及控件单击将添加到’选择中‘。
    131. navigationButtons: false, //如果为true,则在关系图canvas上绘制导航按钮。这些按钮是HTML,可以使用CSS自定义样式。
    132. selectable: true, //用户可以选择节点和边。
    133. selectConnectedEdges: true, //在选中节点时,与其连接的边将高亮(高亮色)显示
    134. tooltipDelay: 300, //提示框显示的延迟时间(毫秒)
    135. zoomView: true,
    136. },
    137. layout: {
    138. randomSeed: 1, //配置每次生成的节点位置都一样,参数为数字12
    139. },
    140. nodes: {
    141. // shape: "circularImage",
    142. shape: "image",
    143. // opacity:0.1,
    144. // borderWidth: 0,
    145. size: 23,
    146. color: {
    147. // border: "rgba(255, 255, 255, 1)",
    148. background: "rgba(102, 51, 255, 0)",
    149. },
    150. font: {
    151. color: "#fff",
    152. },
    153. shadow: {
    154. enabled: true,
    155. // color: "rgba(255, 215, 0, 0.8)",
    156. color: "rgba(153, 102, 204, 0.8)",
    157. size: 40,
    158. x: 0,
    159. y: 0,
    160. },
    161. chosen: {
    162. label: false,
    163. // node: function(values, id, selected, hovering) {
    164. node: function(values) {
    165. values.shadowColor = "rgba(255, 215, 0, 0.8)";
    166. },
    167. },
    168. },
    169. edges: {
    170. hoverWidth: 0,
    171. shadow: true,
    172. arrows: {
    173. to: {
    174. enabled: true,
    175. type: "arrow",
    176. },
    177. },
    178. color: {
    179. color: "#525A81",
    180. highlight: "#FFBA30",
    181. hover: "#FFBA30",
    182. // inherit: "from",
    183. opacity: 1.0,
    184. },
    185. font: {
    186. color: "#fff",
    187. size: 12, // px
    188. strokeWidth: 0, // px
    189. },
    190. },
    191. };
    192. network = new vis.Network(container, data, options);
    193. // 事件监听
    194. this.networkEvent();
    195. },
    196. // 拓扑网络监听事件
    197. networkEvent() {
    198. const _this = this;
    199. //单击
    200. network.on("click", (e) => {
    201. console.log("单击:", e);
    202. this.hideCircle();
    203. });
    204. // 单击鼠标右键
    205. network.on("oncontext", (e) => {
    206. console.log(e);
    207. // 获取节点 id
    208. var nodeId = network.getNodeAt(e.pointer.DOM);
    209. console.log(nodeId, this.nodes.get(nodeId));
    210. console.log("单击:", network.getPositions(nodeId));
    211. e.event.preventDefault();
    212. if (_this.menuShow) {
    213. this.hideCircle();
    214. setTimeout(() => {
    215. if (nodeId) {
    216. this.selectedNode = nodeId;
    217. this.changeMenuPosition(e);
    218. } else {
    219. this.hideCircle();
    220. }
    221. }, 0);
    222. } else {
    223. if (nodeId) {
    224. this.selectedNode = nodeId;
    225. this.changeMenuPosition(e);
    226. } else {
    227. this.hideCircle();
    228. }
    229. }
    230. });
    231. // 鼠标缩放事件
    232. network.on("zoom", (e) => {
    233. console.log(e);
    234. // this.$emit("changeZoom", e.scale);
    235. });
    236. },
    237. getNodePosition() {
    238. // console.log(e);
    239. var nodeId = this.selectedNode;
    240. // 通过节点id 获取节点中心点的在画布的位置
    241. let nodePosition = network.getPositions(nodeId);
    242. // console.log(nodePosition, "nodePosition");
    243. // 坐标转化
    244. nodePosition = network.canvasToDOM(nodePosition[nodeId]);
    245. return nodePosition;
    246. },
    247. // 设置环形位置
    248. setCirclePostion(x, y, scale) {
    249. this.menuShow = true;
    250. this.menuLeft = Math.floor(x) + "px";
    251. this.menuTop = Math.floor(y) + "px";
    252. this.menuScale = scale;
    253. },
    254. // 更新右键菜单位置
    255. changeMenuPosition(e) {
    256. menuTimer = setInterval(() => {
    257. // 获取缩放尺寸
    258. let scale = network.getScale();
    259. let nodePosition = this.getNodePosition(e);
    260. // console.log("nodePosition:", nodePosition);
    261. this.setCirclePostion(
    262. /*
    263. *转换的坐标为相对容器DOM的坐标
    264. *82菜单半径
    265. */
    266. nodePosition.x - 82,
    267. nodePosition.y - 82,
    268. scale
    269. );
    270. }, 10);
    271. },
    272. // 隐藏圆环选项
    273. hideCircle(status) {
    274. window.clearInterval(menuTimer);
    275. // clearInterval(menuTimer);
    276. this.menuShow = false;
    277. if (status) {
    278. //圆环菜单单击隐藏,修改该节点和连接边的展开参数
    279. let edges = [];
    280. this.nodes.updateOnly({
    281. id: this.selectedNode,
    282. open: true,
    283. // changEdge: e.edges[0]
    284. });
    285. edges = network.getConnectedEdges(this.selectedNode);
    286. if (edges.length > 0) {
    287. edges.forEach((item) => {
    288. this.edges.updateOnly({
    289. id: item,
    290. open: true,
    291. // length: 600
    292. });
    293. });
    294. }
    295. var option = {
    296. scale: 1.0,
    297. offset: { x: 0, y: 0 },
    298. animation: {
    299. duration: 1000,
    300. easingFunction: "easeInOutQuad",
    301. },
    302. };
    303. network.focus(this.selectedNode, option);
    304. }
    305. },
    306. },
    307. beforeDestroy() {
    308. window.clearInterval(menuTimer);
    309. },
    310. };
    311. </script>
    312. <style lang="less" scoped>
    313. .box,
    314. .mynetwork2 {
    315. height: 100%;
    316. background-color: #2a004a;
    317. }
    318. .cricle {
    319. position: absolute;
    320. left: 0px;
    321. top: 0px;
    322. width: 100px;
    323. height: 100px;
    324. border: 30px solid rgba(200, 200, 200, 0.7);
    325. border-radius: 50px;
    326. box-sizing: border-box;
    327. z-index: 10;
    328. // display: none;
    329. span {
    330. position: absolute;
    331. left: 0;
    332. right: 0;
    333. width: 30px;
    334. height: 20px;
    335. line-height: 20px;
    336. text-align: center;
    337. font-size: 10px;
    338. cursor: pointer;
    339. &:hover {
    340. color: #1b68ff;
    341. }
    342. &:nth-child(1) {
    343. left: 5px;
    344. top: -25px;
    345. }
    346. &:nth-child(2) {
    347. left: 37px;
    348. top: -5px;
    349. }
    350. &:nth-child(3) {
    351. left: 37px;
    352. top: 25px;
    353. }
    354. &:nth-child(4) {
    355. left: 5px;
    356. top: 44px;
    357. }
    358. &:nth-child(5) {
    359. left: -27px;
    360. top: 25px;
    361. }
    362. &:nth-child(6) {
    363. left: -27px;
    364. top: -5px;
    365. }
    366. }
    367. }
    368. </style>

    菜单代码如下

    1. <template>
    2. <div id="ring-option" class="custom-menu" :style="styleFun" @contextmenu.prevent.capture>
    3. <div class="inner-ring">
    4. <div
    5. :class="['innerWrapper', innerWrapperClass[index]]"
    6. v-for="(item, index) in optionData"
    7. :key="index"
    8. >
    9. <div
    10. :class="[
    11. 'innerRingOption',
    12. innerOptionClass[index],
    13. startRun ? 'start-animation' + (index + 1) : '',
    14. ]"
    15. >
    16. div>
    17. div>
    18. div>
    19. <div class="out-ring" @click="cl('ccc', 2)">
    20. <div
    21. :class="['outWrapper', 'outWrapper' + index]"
    22. v-for="(item, index) in outOptionData"
    23. :key="index"
    24. >
    25. <div
    26. @click="cl(index, 1)"
    27. :class="['outRingOption', 'outOption' + index, classObject + (index + 1)]"
    28. >
    29. <div class="out-option-bg" @click="cl(index, 2)">div>
    30. div>
    31. div>
    32. div>
    33. <div class="inner-option-list" v-if="showOptionListInner">
    34. <div
    35. :class="['option-item', { ashFont: startOut == 1 }]"
    36. v-for="(item, index) in optionData"
    37. :key="index"
    38. @click.stop="close(index + 1, true)"
    39. >
    40. {{ item.name }}
    41. div>
    42. div>
    43. <div class="out-option-list" v-if="showOptionListOut" @click="cl('aa', 2)">
    44. <div
    45. class="option-item"
    46. v-for="(item, index) in outOptionData"
    47. :key="index"
    48. @click.stop="getExtraList(item.key)"
    49. >
    50. {{ item.value }}
    51. div>
    52. div>
    53. div>
    54. template>
    55. <script>
    56. // import Bus from "bus";
    57. // import { getExtra } from "@/api/api.js";
    58. export default {
    59. props: {
    60. menuTop: String,
    61. menuLeft: String,
    62. menuScale: Number,
    63. menuShow: Boolean,
    64. nodeType: String,
    65. delayTime: Number,
    66. },
    67. data() {
    68. return {
    69. startRun: false,
    70. // showDefaultOption: true,
    71. showOptionListInner: false,
    72. optionTimeInner: null,
    73. innerWrapperClass: ["left-top", "right-top", "right-bottom", "left-bottom"],
    74. innerOptionClass: [
    75. "left-top-ring",
    76. "right-top-ring",
    77. "right-bottom-ring",
    78. "left-bottom-ring",
    79. ],
    80. optionData: [{ name: "一度" }, { name: "二度" }, { name: "三度" }, { name: "定向扩展" }],
    81. // 外圈
    82. startOut: 0, //3:不动,1:展开;2收回
    83. optionTimeOut: null,
    84. outOptionData: [
    85. // { value: "同行" },
    86. // { value: "同航班" },
    87. // { value: "同酒店" },
    88. // { value: "同网吧" },
    89. // { value: "同火车" }
    90. ],
    91. showOptionListOut: false,
    92. hasClickOutOption: false, //默认只有点击'选项'才有交互,点击圆盘不交互
    93. };
    94. },
    95. computed: {
    96. styleFun: function() {
    97. console.log(this.menuScale, this.menuLeft, this.menuTop, "menuScale");
    98. var str = `left: ${this.menuLeft}; top: ${this.menuTop};transform:scale(${this.menuScale})`;
    99. return str;
    100. },
    101. scaleFun: function() {
    102. console.log(this.menuScale);
    103. var scale = this.menuScale;
    104. return `transform:scale(${scale})`;
    105. },
    106. classObject: function() {
    107. let classStr = "";
    108. console.log("this.startOut:", this.startOut);
    109. if (this.startOut == 1) {
    110. classStr = "start-out";
    111. } else if (this.startOut == 2) {
    112. classStr = "end-out";
    113. }
    114. return classStr;
    115. },
    116. },
    117. watch: {
    118. menuLeft() {
    119. // console.log(n);
    120. // this.startRun = false;
    121. this.runAnimation();
    122. },
    123. },
    124. mounted() {
    125. this.runAnimation();
    126. clearTimeout(this.optionTime);
    127. this.optionTime = setTimeout(() => {
    128. this.showOptionListInner = true;
    129. // this.showDefaultOption = false;
    130. // console.log("this.showOptionListInner:", this.showOptionListInner);
    131. }, 1000);
    132. },
    133. methods: {
    134. runAnimation() {
    135. // const ringDom = document.querySelectorAll(".innerRingOption");
    136. let delayTime = this.delayTime || 500;
    137. setTimeout(() => {
    138. this.startRun = true;
    139. }, delayTime);
    140. },
    141. close(e, n) {
    142. console.log("++++++", e);
    143. if (n) {
    144. this.hasClickOutOption = n;
    145. }
    146. if (e < 4) {
    147. this.$bus.emit("busGetNodes", e);
    148. this.$emit("closeMenu", true);
    149. // } else if (e == 4 && this.nodeType == "person") {
    150. } else if (e == 4) {
    151. console.log("21:", this.startOut);
    152. clearTimeout(this.optionTimeOut);
    153. if (this.startOut == 0 || this.startOut == 2) {
    154. this.getExtraLabel(); //获取外圈数据
    155. } else if (this.startOut == 1) {
    156. this.startOut = 2; //关闭外圈
    157. this.showOptionListOut = false; //隐藏外圈字
    158. }
    159. console.log("22:", this.startOut);
    160. }
    161. },
    162. getExtraList(e) {
    163. this.startOut = 0; //关闭外圈
    164. this.showOptionListOut = false; //隐藏外圈字
    165. this.$emit("closeMenu", true);
    166. this.$bus.emit("busGetExtraNodes", e);
    167. console.log("----", e);
    168. },
    169. cl(e, d) {
    170. // 测试
    171. // 通过判断点击的位置和各区块的位置界限判断点击事件
    172. // 或者 点击字 执行操作,点击圆环其它地方,关闭圆环
    173. console.log("----", e, d);
    174. },
    175. getExtraLabel() {
    176. // getExtra(this.nodeType).then(res => {
    177. // this.outOptionData = res;
    178. // console.log(res, this.outOptionData);
    179. // if (this.outOptionData.length > 0) {
    180. // this.startOut = 1; //展开外圈
    181. // this.optionTimeOut = setTimeout(() => {
    182. // this.showOptionListOut = true; //显示外圈字
    183. // }, 1000);
    184. // }
    185. // });
    186. },
    187. },
    188. destroyed() {
    189. clearTimeout(this.optionTime);
    190. },
    191. };
    192. script>
    193. <style lang="less" scoped>
    194. @bordercolor: rgba(69, 77, 116);
    195. @keyframes rorateOption {
    196. 0% {
    197. opacity: 0;
    198. }
    199. 100% {
    200. opacity: 1;
    201. }
    202. }
    203. @keyframes rorateOption1 {
    204. 0% {
    205. transform: rotate(-90deg);
    206. opacity: 0;
    207. }
    208. 100% {
    209. transform: rotate(0deg);
    210. opacity: 1;
    211. }
    212. }
    213. @keyframes rorateOption2 {
    214. 0% {
    215. transform: rotate(-180deg);
    216. opacity: 0;
    217. }
    218. 100% {
    219. transform: rotate(0deg);
    220. opacity: 1;
    221. }
    222. }
    223. @keyframes rorateOption3 {
    224. 0% {
    225. transform: rotate(-270deg);
    226. opacity: 0;
    227. }
    228. 100% {
    229. transform: rotate(0deg);
    230. opacity: 1;
    231. }
    232. }
    233. .custom-menu {
    234. width: 164px;
    235. height: 164px;
    236. position: absolute;
    237. // 内圈
    238. .innerWrapper {
    239. width: 82px;
    240. height: 82px;
    241. position: absolute;
    242. // overflow: hidden;
    243. .innerRingOption {
    244. //82px为圆环
    245. // width: 82px;
    246. // height: 82px;
    247. width: 164px;
    248. height: 164px;
    249. // border: 20px solid #45b7ff;
    250. border-style: solid;
    251. border-width: 41px;
    252. border-color: transparent;
    253. border-radius: 50%;
    254. position: absolute;
    255. opacity: 0;
    256. transition-duration: 0.3s;
    257. // &:hover {
    258. // border-width: 50px;
    259. // }
    260. }
    261. .left-top-ring {
    262. border-top-color: @bordercolor;
    263. .option-item {
    264. left: 27px;
    265. top: -27px;
    266. }
    267. }
    268. .right-top-ring {
    269. border-right-color: @bordercolor;
    270. transform: rotate(-90deg);
    271. right: 0;
    272. .option-item {
    273. left: 93px;
    274. top: 22px;
    275. }
    276. }
    277. .right-bottom-ring {
    278. border-bottom-color: @bordercolor;
    279. transform: rotate(-180deg);
    280. right: 0;
    281. bottom: 0;
    282. .option-item {
    283. left: 27px;
    284. top: 90px;
    285. }
    286. }
    287. .left-bottom-ring {
    288. border-left-color: @bordercolor;
    289. transform: rotate(-270deg);
    290. bottom: 0;
    291. .option-item {
    292. left: -32px;
    293. top: 21px;
    294. width: 30px;
    295. }
    296. }
    297. .option-item {
    298. position: absolute;
    299. color: #fff;
    300. cursor: pointer;
    301. }
    302. // .option-item:hover {
    303. // // color: @bordercolor;
    304. // color: #45b7ff;
    305. // font-weight: bold;
    306. // // font-size: 18px;
    307. // }
    308. .start-animation1 {
    309. animation-name: rorateOption;
    310. animation-duration: 0.3s;
    311. animation-fill-mode: forwards;
    312. }
    313. .start-animation2 {
    314. animation-name: rorateOption1;
    315. animation-duration: 0.3s;
    316. animation-fill-mode: forwards;
    317. }
    318. .start-animation3 {
    319. animation-name: rorateOption2;
    320. animation-duration: 0.3s;
    321. animation-fill-mode: forwards;
    322. }
    323. .start-animation4 {
    324. animation-name: rorateOption3;
    325. animation-duration: 0.3s;
    326. animation-fill-mode: forwards;
    327. }
    328. }
    329. .left-top {
    330. left: 0;
    331. top: 0;
    332. }
    333. .right-top {
    334. // left: 82px;
    335. right: 0;
    336. top: 0;
    337. }
    338. .right-bottom {
    339. // left: 82px;
    340. // top: 82px;
    341. right: 0;
    342. bottom: 0;
    343. }
    344. .left-bottom {
    345. left: 0;
    346. bottom: 0;
    347. // top: 82px;
    348. }
    349. .inner-option-list {
    350. position: relative;
    351. width: 164px;
    352. height: 164px;
    353. .option-item {
    354. position: absolute;
    355. cursor: pointer;
    356. color: #fff;
    357. // font-size: 14px;
    358. &:first-child {
    359. left: 68px;
    360. top: 14px;
    361. }
    362. &:nth-child(2) {
    363. left: 129px;
    364. top: 63px;
    365. width: 22px;
    366. }
    367. &:nth-child(3) {
    368. left: 68px;
    369. top: 131px;
    370. }
    371. &:nth-child(4) {
    372. left: 9px;
    373. top: 62px;
    374. width: 30px;
    375. }
    376. }
    377. .option-item:hover {
    378. color: #6b05e8;
    379. // font-weight: bold;
    380. // font-size: 18px;
    381. }
    382. .ashFont {
    383. color: #969ecf;
    384. }
    385. }
    386. // 打开
    387. @keyframes optionOutStart1 {
    388. 0% {
    389. // transform: rotate(0deg) skewX(18deg);
    390. opacity: 0;
    391. }
    392. 100% {
    393. // transform: rotate(72deg) skewX(20deg);
    394. opacity: 1;
    395. }
    396. }
    397. @keyframes optionOutStart2 {
    398. 0% {
    399. transform: rotate(0deg) skewX(20deg);
    400. opacity: 0;
    401. }
    402. 100% {
    403. transform: rotate(72deg) skewX(20deg);
    404. opacity: 1;
    405. }
    406. }
    407. @keyframes optionOutStart3 {
    408. 0% {
    409. transform: rotate(0deg) skewX(20deg);
    410. opacity: 0;
    411. }
    412. 100% {
    413. transform: rotate(144deg) skewX(20deg);
    414. opacity: 1;
    415. }
    416. }
    417. @keyframes optionOutStart4 {
    418. 0% {
    419. transform: rotate(0deg) skewX(20deg);
    420. opacity: 0;
    421. }
    422. 100% {
    423. transform: rotate(216deg) skewX(20deg);
    424. opacity: 1;
    425. }
    426. }
    427. @keyframes optionOutStart5 {
    428. 0% {
    429. transform: rotate(0deg) skewX(20deg);
    430. opacity: 0;
    431. }
    432. 100% {
    433. transform: rotate(288deg) skewX(20deg);
    434. opacity: 1;
    435. }
    436. }
    437. // 关闭
    438. @keyframes optionOutClose1 {
    439. 0% {
    440. // transform: rotate(0deg) skewX(20deg);
    441. opacity: 1;
    442. }
    443. 100% {
    444. // transform: rotate(0deg) skewX(20deg);
    445. opacity: 0;
    446. }
    447. }
    448. @keyframes optionOutClose2 {
    449. 0% {
    450. transform: rotate(72deg) skewX(20deg);
    451. opacity: 1;
    452. }
    453. 100% {
    454. transform: rotate(0deg) skewX(20deg);
    455. opacity: 0;
    456. }
    457. }
    458. @keyframes optionOutClose3 {
    459. 0% {
    460. transform: rotate(144deg) skewX(20deg);
    461. opacity: 1;
    462. }
    463. 100% {
    464. transform: rotate(0deg) skewX(20deg);
    465. opacity: 0;
    466. }
    467. }
    468. @keyframes optionOutClose4 {
    469. 0% {
    470. transform: rotate(216deg) skewX(20deg);
    471. opacity: 1;
    472. }
    473. 100% {
    474. transform: rotate(0deg) skewX(20deg);
    475. opacity: 0;
    476. }
    477. }
    478. @keyframes optionOutClose5 {
    479. 0% {
    480. transform: rotate(288deg) skewX(20deg);
    481. opacity: 1;
    482. }
    483. 100% {
    484. transform: rotate(0deg) skewX(20deg);
    485. opacity: 0;
    486. }
    487. }
    488. // 外圈
    489. .outWrapper {
    490. width: 246px;
    491. height: 246px;
    492. position: absolute;
    493. left: -41px;
    494. top: -41px;
    495. overflow: hidden;
    496. border-radius: 50%;
    497. .outRingOption {
    498. width: 246px;
    499. height: 246px;
    500. position: absolute;
    501. transition-duration: 1s;
    502. // border: 1px solid #fff;
    503. overflow: hidden;
    504. transform: rotate(0deg) skewX(20deg);
    505. transform-origin: 246px 246px;
    506. top: -123px;
    507. left: -123px;
    508. opacity: 0;
    509. // pointer-events: auto;
    510. animation-duration: 1s;
    511. animation-fill-mode: forwards;
    512. }
    513. .out-option-bg {
    514. width: 246px;
    515. height: 246px;
    516. position: absolute;
    517. border: 41px solid;
    518. border-radius: 50%;
    519. // border-color: #464e75a3;
    520. border-color: #454d74;
    521. right: -123px;
    522. bottom: -123px;
    523. transform: skewX(-18deg);
    524. // pointer-events: auto;
    525. }
    526. // .out-option-bg:hover {
    527. // border-color: #ff0000;
    528. // }
    529. // 打开
    530. .start-out1 {
    531. animation-name: optionOutStart1;
    532. }
    533. .start-out2 {
    534. animation-name: optionOutStart2;
    535. }
    536. .start-out3 {
    537. animation-name: optionOutStart3;
    538. }
    539. .start-out4 {
    540. animation-name: optionOutStart4;
    541. }
    542. .start-out5 {
    543. animation-name: optionOutStart5;
    544. }
    545. // 关闭
    546. .end-out1 {
    547. animation-name: optionOutClose1;
    548. }
    549. .end-out2 {
    550. animation-name: optionOutClose2;
    551. }
    552. .end-out3 {
    553. animation-name: optionOutClose3;
    554. }
    555. .end-out4 {
    556. animation-name: optionOutClose4;
    557. }
    558. .end-out5 {
    559. animation-name: optionOutClose5;
    560. }
    561. }
    562. .out-option-list {
    563. position: absolute;
    564. width: 246px;
    565. height: 246px;
    566. left: -41px;
    567. top: -41px;
    568. // pointer-events: none;
    569. .option-item {
    570. position: absolute;
    571. cursor: pointer;
    572. color: #fff;
    573. // pointer-events: none;
    574. &:first-child {
    575. left: 25px;
    576. top: 56px;
    577. transform: rotate(-56deg);
    578. }
    579. &:nth-child(2) {
    580. left: 138px;
    581. top: 18px;
    582. /* width: 22px; */
    583. transform: rotate(20deg);
    584. }
    585. &:nth-child(3) {
    586. left: 212px;
    587. top: 94px;
    588. width: 22px;
    589. }
    590. &:nth-child(4) {
    591. left: 138px;
    592. top: 212px;
    593. /* width: 30px; */
    594. transform: rotate(-14deg);
    595. }
    596. &:nth-child(5) {
    597. left: 24px;
    598. top: 177px;
    599. /* width: 30px; */
    600. transform: rotate(50deg);
    601. }
    602. }
    603. .option-item:hover {
    604. color: #6b05e8;
    605. // font-weight: bold;
    606. // font-size: 18px;
    607. }
    608. }
    609. }
    610. style>

    效果如下

    存在问题

    1. dom元素展开的菜单会遮盖节点

    2. 遮盖超过实际展示的部分

    解决:用canvas绘画菜单(解决2),在initRedraw、beforeDrawing、afterDrawing触发事件中执行绘画

    一、visjs的安装及简单使用

    二、 配置项

    三、 公共API

    四、 事件监听

    五、自定义右键菜单

  • 相关阅读:
    RabbitMQ入门教程(安装,管理插件,Publisher/Consumer/交换机/路由/队列/绑定关系,及如何保证100%投递等)
    kubernetes node 节点管理
    FreeSWITCH 与 Asterisk(译)
    【微机原理笔记】第 7 章 - 常用数字接口电路
    ajax请求的时候get 和post方式的区别?
    android获取进程内存使用信息、一键加速(内存清理)与进程重要级别解析
    外边距合并出现bug的两种情况
    服务器SMP、NUMA、MPP体系学习笔记。
    URLSearchParams快速解析URL查询参数
    网络安全和隐私保护技术
  • 原文地址:https://blog.csdn.net/YouZi_X/article/details/126910157