ElementResize.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  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: 0,
  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: 0,
  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: Boolean,
  79. default: false
  80. }
  81. },
  82. data() {
  83. return {
  84. sizePosOrigin: {
  85. x: 0,
  86. y: 0,
  87. w: 0,
  88. h: 0
  89. },
  90. sizePos: {
  91. x: 0,
  92. y: 0,
  93. w: 0,
  94. h: 0
  95. },
  96. initOver: false,
  97. controlPoints: [],
  98. positionType: "static",
  99. parentNodeSize: {
  100. w: 0,
  101. h: 0
  102. }
  103. };
  104. },
  105. computed: {
  106. styles() {
  107. return this.initOver
  108. ? {
  109. left: this.sizePos.x + "px",
  110. top: this.sizePos.y + "px",
  111. width: this.sizePos.w + "px",
  112. height: this.sizePos.h + "px",
  113. position: this.positionType
  114. }
  115. : {};
  116. },
  117. classes() {
  118. return [
  119. "element-resize",
  120. {
  121. "element-resize-move": this.move,
  122. "element-resize-init": this.initOver
  123. }
  124. ];
  125. }
  126. },
  127. created() {
  128. this.initControlPoints();
  129. },
  130. mounted() {
  131. this.initSize();
  132. },
  133. methods: {
  134. initControlPoints() {
  135. const posName = {
  136. l: "Left",
  137. r: "Right",
  138. t: "Top",
  139. b: "Bottom"
  140. };
  141. this.controlPoints = this.active.map(type => {
  142. const posFullName = type
  143. .split("")
  144. .map(item => {
  145. return posName[item];
  146. })
  147. .join("");
  148. return {
  149. classes: ["control-point", `control-point-${type}`],
  150. movePoint: this[`move${posFullName}Point`],
  151. movePointOver: this.moveOver
  152. };
  153. });
  154. },
  155. initSize() {
  156. const resizeDom = this.$el.childNodes[0];
  157. this.positionType = window.getComputedStyle(resizeDom).position;
  158. this.sizePos = { ...this.value };
  159. this.sizePosOrigin = { ...this.sizePos };
  160. this.initOver = true;
  161. },
  162. checkValidSizePos(sizePos) {
  163. if (
  164. sizePos.w < this.minWidth ||
  165. (this.maxWidth !== 0 && sizePos.w > this.maxWidth)
  166. )
  167. return false;
  168. if (
  169. sizePos.h < this.minHeight ||
  170. (this.maxHeight !== 0 && sizePos.h > this.maxHeight)
  171. )
  172. return false;
  173. // 不同的定位方式,计算方式有差异
  174. const elOffsetTop =
  175. this.positionType === "relative" ? this.$el.offsetTop : sizePos.y;
  176. this.parentNodeSize = {
  177. w: this.$el.offsetParent.offsetWidth,
  178. h: this.$el.offsetParent.offsetHeight
  179. };
  180. if (this.fitParent) {
  181. if (sizePos.x < 0 || elOffsetTop < 0) return false;
  182. if (
  183. sizePos.x + sizePos.w > this.parentNodeSize.w ||
  184. elOffsetTop + sizePos.h > this.parentNodeSize.h
  185. )
  186. return false;
  187. }
  188. return true;
  189. },
  190. getLeftSize(left) {
  191. return {
  192. w: -left + this.sizePosOrigin.w,
  193. x: left + this.sizePosOrigin.x
  194. };
  195. },
  196. getRightSize(left) {
  197. return {
  198. w: left + this.sizePosOrigin.w
  199. };
  200. },
  201. getTopSize(top) {
  202. return {
  203. h: -top + this.sizePosOrigin.h,
  204. y: top + this.sizePosOrigin.y
  205. };
  206. },
  207. getBottomSize(top) {
  208. return {
  209. h: top + this.sizePosOrigin.h
  210. };
  211. },
  212. moveLeftPoint({ left }) {
  213. const sp = { ...this.sizePos, ...this.getLeftSize(left) };
  214. if (this.checkValidSizePos(sp)) {
  215. this.sizePos = { ...sp };
  216. this.emitChange();
  217. }
  218. },
  219. moveRightPoint({ left }) {
  220. const sp = { ...this.sizePos, ...this.getRightSize(left) };
  221. if (this.checkValidSizePos(sp)) {
  222. this.sizePos = { ...sp };
  223. this.emitChange();
  224. }
  225. },
  226. moveTopPoint({ top }) {
  227. const sp = { ...this.sizePos, ...this.getTopSize(top) };
  228. if (this.checkValidSizePos(sp)) {
  229. this.sizePos = { ...sp };
  230. this.emitChange();
  231. }
  232. },
  233. moveBottomPoint({ top }) {
  234. const sp = { ...this.sizePos, ...this.getBottomSize(top) };
  235. if (this.checkValidSizePos(sp)) {
  236. this.sizePos = { ...sp };
  237. this.emitChange();
  238. }
  239. },
  240. moveLeftTopPoint({ left, top }) {
  241. const sp = {
  242. ...this.sizePos,
  243. ...this.getLeftSize(left),
  244. ...this.getTopSize(top)
  245. };
  246. if (this.checkValidSizePos(sp)) {
  247. this.sizePos = { ...sp };
  248. this.emitChange();
  249. }
  250. },
  251. moveRightTopPoint({ left, top }) {
  252. const sp = {
  253. ...this.sizePos,
  254. ...this.getRightSize(left),
  255. ...this.getTopSize(top)
  256. };
  257. if (this.checkValidSizePos(sp)) {
  258. this.sizePos = { ...sp };
  259. this.emitChange();
  260. }
  261. },
  262. moveLeftBottomPoint({ left, top }) {
  263. const sp = {
  264. ...this.sizePos,
  265. ...this.getLeftSize(left),
  266. ...this.getBottomSize(top)
  267. };
  268. if (this.checkValidSizePos(sp)) {
  269. this.sizePos = { ...sp };
  270. this.emitChange();
  271. }
  272. },
  273. moveRightBottomPoint({ left, top }) {
  274. const sp = {
  275. ...this.sizePos,
  276. ...this.getRightSize(left),
  277. ...this.getBottomSize(top)
  278. };
  279. if (this.checkValidSizePos(sp)) {
  280. this.sizePos = { ...sp };
  281. this.emitChange();
  282. }
  283. },
  284. moveOver() {
  285. this.sizePosOrigin = { ...this.sizePos };
  286. this.$emit("resize-over");
  287. },
  288. moveStart() {
  289. this.$emit("on-click");
  290. },
  291. moveElement({ left, top }) {
  292. if (!this.move) return;
  293. const sp = {
  294. ...this.sizePos,
  295. ...{
  296. x: left + this.sizePosOrigin.x,
  297. y: top + this.sizePosOrigin.y
  298. }
  299. };
  300. if (this.checkValidSizePos(sp)) {
  301. this.sizePos = { ...sp };
  302. this.emitChange();
  303. }
  304. },
  305. moveElementOver() {
  306. if (!this.move) return;
  307. this.moveOver();
  308. },
  309. emitChange() {
  310. this.$emit("input", this.sizePos);
  311. this.$emit("change", this.sizePos);
  312. }
  313. }
  314. };
  315. </script>
  316. <style lang="scss" scope>
  317. .element-resize {
  318. position: static;
  319. z-index: auto;
  320. background: #fff;
  321. box-sizing: content-box;
  322. &-move {
  323. cursor: move;
  324. }
  325. &-init {
  326. > div:first-child {
  327. width: 100% !important;
  328. height: 100% !important;
  329. position: relative !important;
  330. top: 0 !important;
  331. left: 0 !important;
  332. overflow: hidden;
  333. }
  334. }
  335. .control-point {
  336. position: absolute;
  337. width: 8px;
  338. height: 8px;
  339. border-radius: 50%;
  340. background: #00a2fe;
  341. z-index: 99;
  342. &-l {
  343. left: 0;
  344. top: 50%;
  345. width: 5px;
  346. height: 20px;
  347. margin-top: -10px;
  348. margin-left: -3px;
  349. border-radius: 0;
  350. padding-top: 3px;
  351. cursor: w-resize;
  352. text-align: center;
  353. color: #fff;
  354. &::before {
  355. content: ".";
  356. display: block;
  357. font-size: 16px;
  358. line-height: 1;
  359. margin-top: -9px;
  360. }
  361. &::after {
  362. content: ".";
  363. display: block;
  364. font-size: 16px;
  365. line-height: 1;
  366. margin-top: -9px;
  367. }
  368. }
  369. &-lt {
  370. left: 0;
  371. top: 0;
  372. margin-top: -5px;
  373. margin-left: -5px;
  374. cursor: nw-resize;
  375. }
  376. &-lb {
  377. left: 0;
  378. bottom: 0;
  379. margin-bottom: -5px;
  380. margin-left: -5px;
  381. cursor: sw-resize;
  382. }
  383. &-r {
  384. right: 0;
  385. top: 50%;
  386. width: 5px;
  387. height: 20px;
  388. margin-top: -10px;
  389. margin-right: -3px;
  390. cursor: e-resize;
  391. border-radius: 0;
  392. padding-top: 3px;
  393. text-align: center;
  394. color: #fff;
  395. &::before {
  396. content: ".";
  397. display: block;
  398. font-size: 16px;
  399. line-height: 1;
  400. margin-top: -9px;
  401. }
  402. &::after {
  403. content: ".";
  404. display: block;
  405. font-size: 16px;
  406. line-height: 1;
  407. margin-top: -9px;
  408. }
  409. }
  410. &-rt {
  411. right: 0;
  412. top: 0;
  413. margin-top: -5px;
  414. margin-right: -5px;
  415. cursor: ne-resize;
  416. }
  417. &-rb {
  418. right: 0;
  419. bottom: 0;
  420. margin-bottom: -5px;
  421. margin-right: -5px;
  422. cursor: se-resize;
  423. }
  424. &-t {
  425. left: 50%;
  426. top: 0;
  427. width: 30px;
  428. height: 5px;
  429. border-radius: 0;
  430. margin-top: -3px;
  431. margin-left: -15px;
  432. cursor: n-resize;
  433. text-align: center;
  434. color: #fff;
  435. &::before {
  436. content: "...";
  437. display: inline-block;
  438. vertical-align: top;
  439. font-size: 16px;
  440. line-height: 1;
  441. margin-top: -10px;
  442. }
  443. }
  444. &-b {
  445. left: 50%;
  446. bottom: 0;
  447. width: 30px;
  448. height: 5px;
  449. border-radius: 0;
  450. margin-bottom: -3px;
  451. margin-left: -15px;
  452. cursor: s-resize;
  453. text-align: center;
  454. color: #fff;
  455. &::before {
  456. content: "...";
  457. display: inline-block;
  458. vertical-align: top;
  459. font-size: 16px;
  460. line-height: 1;
  461. margin-top: -10px;
  462. }
  463. }
  464. }
  465. .control-line {
  466. position: absolute;
  467. z-index: 98;
  468. &-left {
  469. height: 100%;
  470. left: -1px;
  471. top: 0;
  472. border-left: 1px solid #4794b3;
  473. }
  474. &-right {
  475. height: 100%;
  476. right: -1px;
  477. top: 0;
  478. border-left: 1px solid #4794b3;
  479. }
  480. &-top {
  481. width: 100%;
  482. left: 0;
  483. top: -1px;
  484. border-top: 1px solid #4794b3;
  485. }
  486. &-bottom {
  487. width: 100%;
  488. left: 0;
  489. bottom: -1px;
  490. border-top: 1px solid #4794b3;
  491. }
  492. }
  493. }
  494. </style>