DAILY DOCDAILY DOC
Rust
Node
Notes
Ubuntu
Leetcode
  • it-tools
  • excalidraw
  • linux-command
Rust
Node
Notes
Ubuntu
Leetcode
  • it-tools
  • excalidraw
  • linux-command
  • BFC 块级格式化上下文
  • Note
  • WebAssembly
  • public api
  • 位运算
  • bitwise operator
  • css实现隐藏效果
  • css snippets
  • 抖音点赞
  • js 相等判断
  • fetch ReadableStream
  • git
  • Github Actions 工作流
  • google search
  • RPC vs HTTP
  • gravatar
  • hhkb
  • Init project
  • input 文件上传
  • mac

    • Mac 使用技巧
    • alfred
    • mac shortcuts
    • shortcuts text edit
    • mac 修改host
  • 微前端
  • mock
  • nginx dump
  • nginx
  • NirCmd
  • npm
  • Operator Precedence
  • package.json
  • url query 解析
  • pnpm
  • JavaScript Precise countdown
  • react 模版
  • regexp
  • setup web development
  • telegram

    • telegram bot
  • timeFunction ease
  • 视频裁剪
  • vscode

    • vscode 高级指南
    • bracketPairs
    • jsconfig.json
    • vscode pipe into code
    • social project
    • vscode tasks
  • draggable resizable
  • windows 激活
  • 前端截图实现
  • 文本配音 富文本实现
  • 图片处理
  • 前端坐标
  • 定时任务
  • work efficient
  • 微信小程序动画实现方案
  • 排列组合
  • 数列
  • 语音驱动文字
  • 浏览器
  • 状态管理
  • 移动盒子
  • 移动端开发常用snippets
  • 设计模式
  • web performance

draggable resizable

vue-draggable-resizable

index.scss
.selectedArea {
  --icon-width: 10px;
  --icon-height: 10px;
  --icon-color: #fff;
  --icon-active-color: #409eff;

  background: transparent;
  position: absolute;
  box-sizing: border-box;
  //   overflow: hidden;
  cursor: move;
  // border: 1px solid #ffffff;
  //   background: transparent;
  // transition: border 0.1s linear 0s;
  /* 开启硬件加速,提高元素移动的性能 */
  will-change: transform;

  // &:hover {
  //   border-width: 2px;
  //   border-style: solid;
  // }

  // transform-origin: top left;

  .icon {
    position: absolute;
    cursor: crosshair;
    display: none;
    width: var(--icon-width);
    height: var(--icon-height);
    background: var(--icon-active-color);
  }

  .top {
    left: calc(50% - var(--icon-width) / 2);
    // background: linear-gradient(to bottom, #fff 0, #fff 5px, transparent 0);
    cursor: n-resize;
    top: -5px;
  }
  .topLeft {
    left: -5px;
    top: -5px;
    cursor: nw-resize;
    // background: #fff;
    // background: linear-gradient(to right, #fff 0, #fff 100%), linear-gradient(to bottom, #fff 0, #fff 100%);
    // background-size: 100% 5px, 5px 100%;
    // background-position: 0 0, 0 0;
    // background-repeat: no-repeat;
  }
  .topRight {
    top: -5px;
    right: -5px;
    cursor: ne-resize;
    // background: #fff;
    // background: linear-gradient(to left, #fff 0, #fff 5px, transparent 0), linear-gradient(to bottom, #fff 0, #fff 5px, transparent 0);
  }

  .middleLeft {
    top: calc(50% - var(--icon-height) / 2);
    left: -5px;
    cursor: w-resize;
    // background: linear-gradient(to right, #fff 0, #fff 5px, transparent 0);
  }
  .middleRight {
    top: calc(50% - var(--icon-height) / 2);
    right: -5px;
    cursor: e-resize;
    // background: linear-gradient(to left, #fff 0, #fff 5px, transparent 0);
  }
  .bottom {
    left: calc(50% - var(--icon-width) / 2);
    // background: linear-gradient(to top, #fff 0, #fff 5px, transparent 0);
    cursor: s-resize;
    bottom: -5px;
  }
  .bottomLeft {
    left: -5px;
    bottom: -5px;
    cursor: sw-resize;
    // background: linear-gradient(to right, #fff 0, #fff 5px, transparent 0), linear-gradient(to top, #fff 0, #fff 5px, transparent 0);
  }
  .bottomRight {
    bottom: -5px;
    right: -5px;
    cursor: se-resize;
    // background: linear-gradient(to left, #fff 0, #fff 5px, transparent 0), linear-gradient(to top, #fff 0, #fff 5px, transparent 0);
  }

  // }
}
.active {
  // &:hover {
  // 配置虚线背景 以及4条边框
  background-image: linear-gradient(to right, var(--icon-active-color) 50%, transparent 50%),
    linear-gradient(to right, var(--icon-active-color) 50%, transparent 50%);
  background-size: 15px 1px, 15px 1px;
  background-position: 0 0, 0 100%;
  background-repeat: repeat-x;
  &::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;

    background-image: linear-gradient(to bottom, var(--icon-active-color) 50%, transparent 50%),
      linear-gradient(to bottom, var(--icon-active-color) 50%, transparent 50%);
    background-size: 1px 15px, 1px 15px;
    background-position: 0 0, 100% 0;
    background-repeat: repeat-y;
  }
  .icon {
    display: block;
  }
}
.inputBox {
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  border: solid 1px red;
  width: 100%;
  height: 30px;
  z-index: 100;
  font-size: 18px;
  cursor: move;
  color: #fff;
}

index.vue
<template>
  <!-- 用户选择的区域大小 -->
  <div
    ondragstart="return false"
    :class="['selectedArea', active ? 'active' : '']"
    ref="box"
    :data-elementUid="uid"
    :style="{
      transform: `translate(${l}px,${t}px)`,
      width: w + 'px',
      height: h + 'px',
      fontSize: fontSizeChange + 'px',
    }"
    id="movearea"
    data-action="move"
    @click.stop.prevent="() => {}"
    @mousedown.stop="mousedown"
  >
    <slot></slot>
    <!-- {{ videoWidth }} {{ videoHeight }} -->
    <!-- 以下内容为 8 个操作点位 -->
    <div data-action="resize" data-direction="top" class="icon top"></div>
    <div data-action="resize" data-direction="left" class="icon middleLeft"></div>
    <div data-action="resize" data-direction="right" class="icon middleRight"></div>
    <div data-action="resize" data-direction="bottom" class="icon bottom"></div>
    <div data-action="resize" data-direction="top,left" class="icon topLeft"></div>
    <div data-action="resize" data-direction="top,right" class="icon topRight"></div>
    <div data-action="resize" data-direction="bottom,left" class="icon bottomLeft"></div>
    <div data-action="resize" data-direction="bottom,right" class="icon bottomRight"></div>
  </div>
</template>
<script>
import { throttle } from '../../utils/bus';
export default {
  name: 'draggable-resizable',
  props: {
    updateCurrentPage: Function,
    activeUid: {
      type: String,
      require: false,
    },
    index: {
      type: Number,
      require: true,
    },
    scalingRatio: {
      type: Number,
      default: 1,
      required: true,
    },
    active: {
      type: Boolean,
    },
    element: {
      type: Object,
      required: true,
    },
    maxLeft: {
      type: Number,
      required: true,
    },
    maxTop: {
      type: Number,
      required: true,
    },
    minWidth: {
      type: Number,
      default: 50,
    },
    minHeight: {
      type: Number,
      default: 50,
    },
  },
  data() {
    return {
      // 坐标
      l: 0,
      t: 0,
      w: 0,
      h: 0,
      draggingElement: null, // 当前点击开始选中元素
      mouseStart: {
        x: 0,
        y: 0,
        offsetX: 0,
        offsetY: 0,
        prevTop: 0,
        prevLeft: 0,
        prevRight: 0,
        prevBottom: 0,
      }, // 记录鼠标起点位置坐标
      resizeDirection: '', // 记录当前操作方位
      minSize: 25, //
    };
  },
  computed: {
    uid() {
      return this.element.uid;
    },
    width() {
      return this.element.width;
    },
    height() {
      return this.element.height;
    },
    top() {
      return this.element.top;
    },
    left() {
      return this.element.left;
    },

    ratio() {
      return this.width / this.height; // 宽高 比例
    },
    fontSizeChange() {
      let ratio = 0.5; // 倍数
      let desiredFontSize = 16; //字体大小
      return (Math.min(this.w * ratio, this.h) * desiredFontSize) / 100; //计算fontSize 值
    },
  },
  watch: {
    scalingRatio: {
      handler: function (val) {
        this.l = this.left * val;
        this.t = this.top * val;
        this.w = this.width * val;
        this.h = this.height * val;
        // console.log('scalingRatio', val);
      },
      // deep: true,
      immediate: true,
    },
    left: {
      handler: function (val) {
        this.l = val * this.scalingRatio;
      },
      // deep: true,
      immediate: true,
    },
    top: {
      handler: function (val) {
        this.t = val * this.scalingRatio;
      },
      // deep: true,
      immediate: true,
    },
    width: {
      handler: function (val) {
        this.w = val * this.scalingRatio;
      },
      // deep: true,
      immediate: true,
    },
    height: {
      handler: function (val) {
        this.h = val * this.scalingRatio;
      },
      // deep: true,
      immediate: true,
    },
  },
  mounted() {
    // 监听鼠标移动事件
    document.addEventListener('mousemove', throttle(this.mouseMove, 30));
    // 移除拖拽状态
    document.addEventListener('mouseup', this.mouseUp);
  },
  beforeDestroy() {
    // WARNNING 注销事件
    document.removeEventListener('mouseup', this.mouseMove);
    document.removeEventListener('mousemove', this.mouseUp);
  },
  methods: {
    dbClickElement() {
      console.log('dbClickElement');
      this.updateCurrentPage(page => {
        page.activeElementsUid = this.uid;
      });
    },
    highlightElement() {
      // 数字人需要双击切换回蒙版
      //   if (this.element.type == 'avatar') {
      //     return;
      //   }
      this.updateCurrentPage(page => {
        page.activeElementsUid = this.uid;
      });
    },
    inputBlurClick() {
      console.log('失去焦点');
    },
    // resetPos(left, top, right, bottom) {
    //   this.top = left;
    //   this.left = top;
    //   this.right = right;
    //   this.bottom = bottom;
    // },
    mouseUp() {
      if (!this.draggingElement) return;
      this.positionChange();
      this.draggingElement = null;
      this.resizeDirection = '';
      this.updateBubbleText();
    },
    mouseMove(event) {
      // 蒙版数字人,禁用掉 dom 拖拽,高亮后 切换到 canvas 拖拽
      if (this.element.type == 'avatar') {
        return;
      }

      if (!this.draggingElement) return;

      // eslint-disable-next-line no-unused-vars
      let { action = '' } = this.draggingElement.dataset;
      const curAction = event.target.dataset.action;
      action = action ? action : curAction;

      // 拖拽组件里面包含多个 子元素,拖拽的元素是移动元素的子元素, 默认move
      if (this.$refs.box.contains(this.draggingElement)) {
        action = action ? action : 'move';
      }

      // 移动操作, 更新元素的位置
      // console.log('mousemove', this.draggingElement.dataset.action, event.target.dataset.action);
      if (action == 'move') {
        // 过滤掉点击其他地方,拖动到改区域的时候 触发移动
        // if (event.target !== this.draggingElement) return;
        // 计算元素的新位置
        const { x, y, prevTop, prevLeft, prevWidth, prevHeight } = this.mouseStart;
        const minSize = this.minSize;
        let newX = event.clientX - x + prevLeft;
        let newY = event.clientY - y + prevTop;

        let minX = -prevWidth + minSize;
        let minY = -prevHeight + minSize;
        let maxX = this.maxLeft - minSize;
        let maxY = this.maxTop - minSize;

        // console.log(event.target.dataset.action);

        // 限制移动范围
        newX = Math.max(minX, Math.min(newX, maxX));
        newY = Math.max(minY, Math.min(newY, maxY));
        // 需要同时移动 2个点位
        // console.log('x,y', newX, newY);
        this.l = newX;
        this.t = newY;
      } else if (action == 'resize') {
        this.resizeBox(event);
      }
    },
    resizeBox(e) {
      let { direction = '' } = e.target.dataset;
      // 起点不是
      if (this.draggingElement == null || this.draggingElement.dataset.action !== 'resize') return;

      // 连续的拖拽,超过边界后还是保持拖拽动作
      if (direction && this.resizeDirection == '') {
        this.resizeDirection = direction;
      }
      direction = this.resizeDirection;
      if (!direction) return;

      const { clientX: x, clientY: y } = e;
      // eslint-disable-next-line no-unused-vars
      const { x: oldX, y: oldY } = this.mouseStart;

      // console.log('direction', direction);
      const moveX = x - oldX,
        moveY = y - oldY;

      if (direction == 'top') {
        this.moveTop(moveX, moveY);
        // this.moveTopLeft(moveX, moveY);
      } else if (direction == 'bottom') {
        // this.moveBottom(moveY);
        this.moveBottom(moveX, moveY);
      } else if (direction == 'left') {
        this.moveLeft(moveX);
      } else if (direction == 'right') {
        this.moveRight(moveX);
      } else if (direction == 'top,left') {
        this.moveTopLeft(moveX, moveY);
      } else if (direction == 'top,right') {
        this.moveTopRight(moveX, moveY);
      } else if (direction == 'bottom,left') {
        this.moveBottomLeft(moveX, moveY);
      } else if (direction == 'bottom,right') {
        this.moveBottomRight(moveX, moveY);
      }
      this.$emit('resize', this.element);
    },
    updateBubbleText() {
      //   console.log('updateBubbleText');
      // this.updateCurrentPage(page => {
      //   for (const el of page.elements) {
      //     if (el.uid == this.uid) {
      //       const item = el.item;
      //       item.fontSize = this.fontSize;
      //       item.oldW = el.width;
      //       item.oldH = el.height;
      //     }
      //   }
      // });
    },
    positionChange() {
      const { width, height, left, top, index } = this.calculateVideoPos();
      this.updateCurrentPage(page => {
        for (const el of page.elements) {
          if (el.uid == this.uid) {
            el.width = width;
            el.height = height;
            el.left = left;
            el.top = top;
          }
        }
      });
      this.$emit('change', this.element);
    },
    moveTop(moveX, moveY) {
      const { prevLeft, prevWidth, prevHeight, prevTop } = this.mouseStart;
      // 获取中心点坐标
      const prevCenterX = prevLeft + prevWidth / 2,
        prevCenterY = prevTop + prevHeight / 2;

      const ratio = this.ratio;
      this.h = Math.max(prevHeight - moveY, this.minHeight);
      this.w = this.h * ratio;
      this.t = prevCenterY - this.h / 2;
      this.l = prevCenterX - this.w / 2;
      this.updateBubbleText();
      // this.t = Math.min(prevTop - moveY, this.maxTop - this.minSize);
    },
    moveBottom(moveX, moveY) {
      const { prevLeft, prevWidth, prevHeight, prevTop } = this.mouseStart;
      // 获取中心点坐标
      const prevCenterX = prevLeft + prevWidth / 2,
        prevCenterY = prevTop + prevHeight / 2;

      const ratio = this.ratio;
      this.h = Math.max(prevHeight + moveY, this.minHeight);
      this.w = this.h * ratio;
      this.t = prevCenterY - this.h / 2;
      this.l = prevCenterX - this.w / 2;
      this.updateBubbleText();
      // this.t = Math.min(prevTop - moveY, this.maxTop - this.minSize);
    },
    getPrevCenterPos() {
      const { prevLeft, prevWidth, prevHeight, prevTop } = this.mouseStart;
      // 获取中心点坐标
      const prevCenterX = prevLeft + prevWidth / 2,
        prevCenterY = prevTop + prevHeight / 2;
      return { x: prevCenterX, y: prevCenterY };
    },
    moveLeft(moveX) {
      const { prevLeft, prevWidth, prevHeight, prevTop } = this.mouseStart;
      // 获取中心点坐标
      const prevCenterX = prevLeft + prevWidth / 2,
        prevCenterY = prevTop + prevHeight / 2;

      const ratio = this.ratio;
      this.w = Math.max(prevWidth - moveX, this.minWidth);
      this.h = this.w / ratio;
      this.t = prevCenterY - this.h / 2;
      this.l = prevCenterX - this.w / 2;
      this.updateBubbleText();
      // const { prevLeft, prevWidth } = this.mouseStart;
      // const ratio = this.ratio;
      // this.w = Math.max(prevWidth - moveX, this.minWidth);
      // this.h = this.w / ratio;
      // // this.t = Math.min(prevTop - moveY, this.maxTop - this.minSize);
      // this.l = prevLeft + prevWidth - this.w;
    },
    moveRight(moveX) {
      const { prevLeft, prevWidth, prevHeight, prevTop } = this.mouseStart;
      // 获取中心点坐标
      const prevCenterX = prevLeft + prevWidth / 2,
        prevCenterY = prevTop + prevHeight / 2;

      const ratio = this.ratio;
      this.w = Math.max(prevWidth + moveX, this.minWidth);
      this.h = this.w / ratio;
      this.t = prevCenterY - this.h / 2;
      this.l = prevCenterX - this.w / 2;

      this.updateBubbleText();
    },
    moveTopLeft(moveX, moveY) {
      const { prevLeft, prevWidth, prevTop, prevHeight } = this.mouseStart;

      const ratio = this.ratio;
      this.h = Math.max(prevHeight - moveY, this.minHeight);
      this.w = this.h * ratio;
      // 1. 保证右下角坐标不变动
      this.t = prevTop + prevHeight - this.h;
      this.l = prevLeft + prevWidth - this.w;
      this.updateBubbleText();
    },
    moveTopRight(moveX, moveY) {
      const { prevTop, prevLeft, prevHeight } = this.mouseStart;
      const ratio = this.ratio;
      this.h = Math.max(prevHeight - moveY, this.minHeight);
      this.w = this.h * ratio;
      // 保证左下角坐标位置不变
      this.t = prevTop + prevHeight - this.h;
      this.l = prevLeft;

      this.updateBubbleText();
    },
    moveBottomLeft(moveX, moveY) {
      const { prevLeft, prevWidth, prevHeight } = this.mouseStart;

      const ratio = this.ratio;
      this.h = Math.max(prevHeight + moveY, this.minHeight);
      this.w = this.h * ratio;
      // this.t = Math.min(prevTop - moveY, this.maxTop - this.minSize);
      this.l = prevLeft + prevWidth - this.w;

      this.updateBubbleText();
    },
    moveBottomRight(moveX, moveY) {
      const { prevHeight } = this.mouseStart;
      const ratio = this.ratio;
      this.h = Math.max(prevHeight + moveY, this.minHeight);
      this.w = this.h * ratio;

      this.updateBubbleText();
      // this.t = Math.min(prevTop - moveY, this.maxTop - this.minSize);
      // this.l = prevLeft + prevWidth - this.w;
    },
    mousedown(e) {
      this.draggingElement = e.target;
      const box = this.$refs.box;

      const rect = box.getBoundingClientRect();
      // 计算鼠标与元素左上角的偏移量
      const x = e.clientX - rect.left;
      const y = e.clientY - rect.top;
      this.mouseStart = {
        x: e.clientX,
        y: e.clientY,
        offsetX: x,
        offsetY: y,
        prevTop: this.t,
        prevLeft: this.l,
        prevWidth: this.w,
        prevHeight: this.h,
      };
      this.highlightElement();
    },
    reflectCropperPos(val) {
      let { top, left, width, height } = val;
      this.l = left * this.scalingRatio;
      this.t = top * this.scalingRatio;
      this.w = width * this.scalingRatio;
      this.h = height * this.scalingRatio;
    },
    calculateVideoPos() {
      let { t, l, w, h, scalingRatio, index } = this;
      // console.log('w--->',w)
      // 计算出 参考 w/h 160/90 的尺寸
      return {
        index,
        width: w / scalingRatio,
        height: h / scalingRatio,
        left: l / scalingRatio,
        top: t / scalingRatio,
      };
    },
  },
};
</script>

<style lang="scss" src="./index.scss" scoped></style>
Last Updated:
Contributors: rosendo
Next
windows 激活