ElementResize.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. <template>
  2. <div
  3. :class="classes"
  4. :style="styles"
  5. v-move-ele.prevent.stop="{
  6. moveStart,
  7. moveElement,
  8. moveStop: moveElementOver
  9. }"
  10. >
  11. <slot></slot>
  12. <div class="resize-control">
  13. <div
  14. v-for="(control, index) in controlPoints"
  15. :key="index"
  16. :class="control.classes"
  17. v-move-ele.prevent.stop="{
  18. moveElement: control.movePoint,
  19. moveStop: control.movePointOver
  20. }"
  21. ></div>
  22. <div class="control-line control-line-left"></div>
  23. <div class="control-line control-line-right"></div>
  24. <div class="control-line control-line-top"></div>
  25. <div class="control-line control-line-bottom"></div>
  26. </div>
  27. </div>
  28. </template>
  29. <script>
  30. import MoveEle from "../../directives/move-ele";
  31. export default {
  32. name: "element-resize",
  33. directives: { MoveEle },
  34. props: {
  35. value: {
  36. type: Object,
  37. required: true
  38. },
  39. active: {
  40. type: Array,
  41. default() {
  42. return ["r", "rb", "b", "lb", "l", "lt", "t", "rt"];
  43. }
  44. },
  45. move: {
  46. type: Boolean,
  47. default: true
  48. },
  49. minWidth: {
  50. type: Number,
  51. default: 30,
  52. validator(val) {
  53. return val >= 0;
  54. }
  55. },
  56. maxWidth: {
  57. type: Number,
  58. default: 0,
  59. validator(val) {
  60. return val >= 0;
  61. }
  62. },
  63. minHeight: {
  64. type: Number,
  65. default: 30,
  66. validator(val) {
  67. return val >= 0;
  68. }
  69. },
  70. maxHeight: {
  71. type: Number,
  72. default: 0,
  73. validator(val) {
  74. return val >= 0;
  75. }
  76. },
  77. fitParent: {
  78. type: Array,
  79. default() {
  80. return ["w", "h"];
  81. }
  82. },
  83. isCompact: {
  84. type: Boolean,
  85. default: false
  86. },
  87. transformFit: {
  88. type: Function
  89. },
  90. elementPk: {
  91. type: String,
  92. default: ""
  93. }
  94. },
  95. data() {
  96. return {
  97. sizePosOrigin: {
  98. x: 0,
  99. y: 0,
  100. w: 0,
  101. h: 0
  102. },
  103. offsetTopOrigin: 0,
  104. sizePos: {
  105. x: 0,
  106. y: 0,
  107. w: 0,
  108. h: 0
  109. },
  110. lastSizePos: {},
  111. initOver: false,
  112. controlPoints: [],
  113. positionType: "static",
  114. parentNodeSize: {
  115. w: 0,
  116. h: 0
  117. }
  118. };
  119. },
  120. computed: {
  121. styles() {
  122. return this.initOver
  123. ? {
  124. left: this.sizePos.x + "px",
  125. top: this.sizePos.y + "px",
  126. width: this.sizePos.w + "px",
  127. height: this.sizePos.h + "px",
  128. position: this.positionType
  129. }
  130. : {};
  131. },
  132. classes() {
  133. return [
  134. "element-resize",
  135. {
  136. "element-resize-move": this.move,
  137. "element-resize-init": this.initOver,
  138. "element-resize-compact": this.isCompact
  139. }
  140. ];
  141. },
  142. fitParentTypeWidth() {
  143. return this.fitParent.includes("w");
  144. },
  145. fitParentTypeHeight() {
  146. return this.fitParent.includes("h");
  147. }
  148. },
  149. created() {
  150. this.initControlPoints();
  151. },
  152. mounted() {
  153. this.initSize();
  154. },
  155. methods: {
  156. initControlPoints() {
  157. const posName = {
  158. l: "Left",
  159. r: "Right",
  160. t: "Top",
  161. b: "Bottom"
  162. };
  163. this.controlPoints = this.active.map(type => {
  164. const posFullName = type
  165. .split("")
  166. .map(item => {
  167. return posName[item];
  168. })
  169. .join("");
  170. return {
  171. classes: ["control-point", `control-point-${type}`],
  172. movePoint: this[`move${posFullName}Point`],
  173. movePointOver: this.moveOver
  174. };
  175. });
  176. },
  177. initSize() {
  178. const resizeDom = this.$el.childNodes[0];
  179. this.positionType = window.getComputedStyle(resizeDom).position;
  180. this.sizePos = { ...this.value };
  181. this.lastSizePos = { ...this.value };
  182. this.sizePosOrigin = { ...this.value };
  183. if (this.positionType === "relative")
  184. this.offsetTopOrigin = this.$el.offsetTop;
  185. this.initOver = true;
  186. },
  187. fetchValidSizePos(sizePos, actionType) {
  188. if (sizePos.w <= this.minWidth) {
  189. sizePos.w = this.minWidth;
  190. }
  191. if (this.maxWidth !== 0 && sizePos.w >= this.maxWidth) {
  192. sizePos.w = this.maxWidth;
  193. }
  194. if (sizePos.h <= this.minHeight) {
  195. sizePos.h = this.minHeight;
  196. }
  197. if (this.maxHeight !== 0 && sizePos.h >= this.maxHeight) {
  198. sizePos.h = this.maxHeight;
  199. }
  200. if (!this.fitParent.length) {
  201. this.lastSizePos = { ...sizePos };
  202. return sizePos;
  203. }
  204. // 不同的定位方式,计算方式有差异
  205. this.parentNodeSize = {
  206. w: this.$el.offsetParent.offsetWidth,
  207. h: this.$el.offsetParent.offsetHeight
  208. };
  209. if (this.fitParentTypeWidth) {
  210. if (sizePos.x <= 0) {
  211. sizePos.x = 0;
  212. if (actionType.includes("left")) sizePos.w = this.lastSizePos.w;
  213. }
  214. if (sizePos.x + sizePos.w > this.parentNodeSize.w) {
  215. sizePos.x = this.lastSizePos.x;
  216. sizePos.w = this.parentNodeSize.w - sizePos.x;
  217. }
  218. }
  219. if (this.fitParentTypeHeight) {
  220. if (this.positionType === "relative") {
  221. const elOffsetTop = this.$el.offsetTop;
  222. if (this.sizePosOrigin.y - sizePos.y >= this.offsetTopOrigin) {
  223. sizePos.h = this.lastSizePos.h;
  224. sizePos.y = this.sizePosOrigin.y - this.offsetTopOrigin;
  225. }
  226. if (elOffsetTop + sizePos.h >= this.parentNodeSize.h) {
  227. sizePos.y = this.lastSizePos.y;
  228. sizePos.h = this.lastSizePos.h;
  229. // TODO:这里如果拖动太快,会有问题
  230. // console.log(this.parentNodeSize.h, elOffsetTop, sizePos.h);
  231. }
  232. } else {
  233. if (sizePos.y <= 0) {
  234. sizePos.y = 0;
  235. if (actionType.includes("top")) sizePos.h = this.lastSizePos.h;
  236. }
  237. if (sizePos.y + sizePos.h > this.parentNodeSize.h) {
  238. sizePos.y = this.lastSizePos.y;
  239. sizePos.h = this.parentNodeSize.h - sizePos.y;
  240. }
  241. }
  242. }
  243. this.lastSizePos = { ...sizePos };
  244. return this.transformFit
  245. ? this.transformFit({ id: this.elementPk, ...sizePos }, actionType)
  246. : sizePos;
  247. },
  248. getLeftSize(left) {
  249. return {
  250. w: -left + this.sizePosOrigin.w,
  251. x: left + this.sizePosOrigin.x
  252. };
  253. },
  254. getRightSize(left) {
  255. return {
  256. w: left + this.sizePosOrigin.w
  257. };
  258. },
  259. getTopSize(top) {
  260. return {
  261. h: -top + this.sizePosOrigin.h,
  262. y: top + this.sizePosOrigin.y
  263. };
  264. },
  265. getBottomSize(top) {
  266. return {
  267. h: top + this.sizePosOrigin.h
  268. };
  269. },
  270. moveLeftPoint({ left }) {
  271. const sp = { ...this.sizePos, ...this.getLeftSize(left) };
  272. this.sizePos = { ...this.fetchValidSizePos(sp, "left") };
  273. this.emitChange();
  274. },
  275. moveRightPoint({ left }) {
  276. const sp = { ...this.sizePos, ...this.getRightSize(left) };
  277. this.sizePos = { ...this.fetchValidSizePos(sp, "right") };
  278. this.emitChange();
  279. },
  280. moveTopPoint({ top }) {
  281. const sp = { ...this.sizePos, ...this.getTopSize(top) };
  282. this.sizePos = { ...this.fetchValidSizePos(sp, "top") };
  283. this.emitChange();
  284. },
  285. moveBottomPoint({ top }) {
  286. const sp = { ...this.sizePos, ...this.getBottomSize(top) };
  287. this.sizePos = { ...this.fetchValidSizePos(sp, "bottom") };
  288. this.emitChange();
  289. },
  290. moveLeftTopPoint({ left, top }) {
  291. const sp = {
  292. ...this.sizePos,
  293. ...this.getLeftSize(left),
  294. ...this.getTopSize(top)
  295. };
  296. this.sizePos = { ...this.fetchValidSizePos(sp, "left-top") };
  297. this.emitChange();
  298. },
  299. moveRightTopPoint({ left, top }) {
  300. const sp = {
  301. ...this.sizePos,
  302. ...this.getRightSize(left),
  303. ...this.getTopSize(top)
  304. };
  305. this.sizePos = { ...this.fetchValidSizePos(sp, "right-top") };
  306. this.emitChange();
  307. },
  308. moveLeftBottomPoint({ left, top }) {
  309. const sp = {
  310. ...this.sizePos,
  311. ...this.getLeftSize(left),
  312. ...this.getBottomSize(top)
  313. };
  314. this.sizePos = { ...this.fetchValidSizePos(sp, "left-bottom") };
  315. this.emitChange();
  316. },
  317. moveRightBottomPoint({ left, top }) {
  318. const sp = {
  319. ...this.sizePos,
  320. ...this.getRightSize(left),
  321. ...this.getBottomSize(top)
  322. };
  323. this.sizePos = { ...this.fetchValidSizePos(sp, "right-bottom") };
  324. this.emitChange();
  325. },
  326. moveOver() {
  327. this.sizePosOrigin = { ...this.sizePos };
  328. this.lastSizePos = { ...this.sizePos };
  329. if (this.positionType === "relative")
  330. this.offsetTopOrigin = this.$el.offsetTop < 0 ? 0 : this.$el.offsetTop;
  331. this.$emit("resize-over", this.sizePos);
  332. },
  333. moveStart() {
  334. this.$emit("on-click");
  335. },
  336. moveElement({ left, top }) {
  337. if (!this.move) return;
  338. const sp = {
  339. ...this.sizePos,
  340. ...{
  341. x: left + this.sizePosOrigin.x,
  342. y: top + this.sizePosOrigin.y
  343. }
  344. };
  345. this.sizePos = { ...this.fetchValidSizePos(sp, "move") };
  346. this.emitChange();
  347. },
  348. moveElementOver() {
  349. if (!this.move) return;
  350. this.moveOver();
  351. },
  352. emitChange() {
  353. this.$emit("input", this.sizePos);
  354. this.$emit("change", this.sizePos);
  355. }
  356. }
  357. };
  358. </script>
  359. <style lang="scss" scope>
  360. .element-resize {
  361. position: static;
  362. z-index: auto;
  363. background: #fff;
  364. box-sizing: content-box;
  365. &-move {
  366. cursor: move;
  367. }
  368. &-init {
  369. > div:first-child {
  370. width: 100% !important;
  371. height: 100% !important;
  372. position: relative !important;
  373. top: 0 !important;
  374. left: 0 !important;
  375. overflow: hidden;
  376. }
  377. }
  378. .control-point {
  379. position: absolute;
  380. width: 8px;
  381. height: 8px;
  382. border-radius: 50%;
  383. background: #00a2fe;
  384. z-index: 99;
  385. &-l {
  386. left: 0;
  387. top: 50%;
  388. width: 5px;
  389. height: 20px;
  390. margin-top: -10px;
  391. margin-left: -3px;
  392. border-radius: 0;
  393. padding-top: 3px;
  394. cursor: w-resize;
  395. text-align: center;
  396. color: #fff;
  397. &::before {
  398. content: ".";
  399. display: block;
  400. font-size: 16px;
  401. line-height: 1;
  402. margin-top: -9px;
  403. }
  404. &::after {
  405. content: ".";
  406. display: block;
  407. font-size: 16px;
  408. line-height: 1;
  409. margin-top: -9px;
  410. }
  411. }
  412. &-lt {
  413. left: 0;
  414. top: 0;
  415. margin-top: -5px;
  416. margin-left: -5px;
  417. cursor: nw-resize;
  418. }
  419. &-lb {
  420. left: 0;
  421. bottom: 0;
  422. margin-bottom: -5px;
  423. margin-left: -5px;
  424. cursor: sw-resize;
  425. }
  426. &-r {
  427. right: 0;
  428. top: 50%;
  429. width: 5px;
  430. height: 20px;
  431. margin-top: -10px;
  432. margin-right: -3px;
  433. cursor: e-resize;
  434. border-radius: 0;
  435. padding-top: 3px;
  436. text-align: center;
  437. color: #fff;
  438. &::before {
  439. content: ".";
  440. display: block;
  441. font-size: 16px;
  442. line-height: 1;
  443. margin-top: -9px;
  444. }
  445. &::after {
  446. content: ".";
  447. display: block;
  448. font-size: 16px;
  449. line-height: 1;
  450. margin-top: -9px;
  451. }
  452. }
  453. &-rt {
  454. right: 0;
  455. top: 0;
  456. margin-top: -5px;
  457. margin-right: -5px;
  458. cursor: ne-resize;
  459. }
  460. &-rb {
  461. right: 0;
  462. bottom: 0;
  463. margin-bottom: -5px;
  464. margin-right: -5px;
  465. cursor: se-resize;
  466. }
  467. &-t {
  468. left: 50%;
  469. top: 0;
  470. width: 30px;
  471. height: 5px;
  472. border-radius: 0;
  473. margin-top: -3px;
  474. margin-left: -15px;
  475. cursor: n-resize;
  476. text-align: center;
  477. color: #fff;
  478. &::before {
  479. content: "...";
  480. display: inline-block;
  481. vertical-align: top;
  482. font-size: 16px;
  483. line-height: 1;
  484. margin-top: -10px;
  485. }
  486. }
  487. &-b {
  488. left: 50%;
  489. bottom: 0;
  490. width: 30px;
  491. height: 5px;
  492. border-radius: 0;
  493. margin-bottom: -3px;
  494. margin-left: -15px;
  495. cursor: s-resize;
  496. text-align: center;
  497. color: #fff;
  498. &::before {
  499. content: "...";
  500. display: inline-block;
  501. vertical-align: top;
  502. font-size: 16px;
  503. line-height: 1;
  504. margin-top: -10px;
  505. }
  506. }
  507. }
  508. .control-line {
  509. position: absolute;
  510. z-index: 98;
  511. &-left {
  512. height: 100%;
  513. left: -1px;
  514. top: 0;
  515. border-left: 1px solid #4794b3;
  516. }
  517. &-right {
  518. height: 100%;
  519. right: -1px;
  520. top: 0;
  521. border-left: 1px solid #4794b3;
  522. }
  523. &-top {
  524. width: 100%;
  525. left: 0;
  526. top: -1px;
  527. border-top: 1px solid #4794b3;
  528. }
  529. &-bottom {
  530. width: 100%;
  531. left: 0;
  532. bottom: -1px;
  533. border-top: 1px solid #4794b3;
  534. }
  535. }
  536. &-compact {
  537. .control-line {
  538. &-left {
  539. left: 0;
  540. border-left: 1px dashed #bbb;
  541. }
  542. &-right {
  543. right: 0;
  544. border-left: 1px dashed #bbb;
  545. }
  546. &-top {
  547. top: 0;
  548. border-top: 1px dashed #bbb;
  549. }
  550. &-bottom {
  551. bottom: 0;
  552. border-top: 1px dashed #bbb;
  553. }
  554. }
  555. }
  556. }
  557. </style>