代码 基于Vue.js的拖动功能和画布设置的Web应用

2024-11-19 09:53:07 +0800 CST views 534

该文本描述了一个基于Vue.js的拖动功能和画布设置的Web应用。用户可以通过左侧的功能菜单添加文字或图片元素到画布中,并可以调整这些元素的样式和位置。应用提供了设置画布的宽度、高度、背景颜色和背景图片的功能,同时支持预览和下载画布内容的功能。整体设计旨在提供一个直观的用户界面,方便用户进行图形设计。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>拖动功能与画布设置</title>
  <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
  <style>
    .preview-area {
      position: relative;
      margin: 20px 0;
      background-color: #f8f9fa;
      border: 1px solid #ccc;
      padding: 10px;
      overflow: hidden;
    }

    .draggable-element {
      position: absolute;
      cursor: move;
      display: flex;
      justify-content: center;
      align-items: center;
    }

    .modal-content {
      width: 100%;
    }

    .settings-panel input,
    .settings-panel label {
      width: 100%;
      margin-bottom: 10px;
    }

    #canvasContainer {
      margin: 20px auto;
      border: 1px solid #ccc;
      position: relative;
      background-color: white;
    }

    .sidebar {
      background-color: #f4f4f4;
      padding: 20px;
      border-right: 1px solid #ddd;
      height: 100vh;
    }

    .tool {
      margin-bottom: 15px;
          margin-right: 10px;
    }

    .preview-title {
      text-align: center;
      font-weight: bold;
      margin-bottom: 15px;
    }

    .btn-group {
      margin-bottom: 15px;
    }
  </style>
</head>
<body>

<div id="app" class="container-fluid">
  <!-- 顶部菜单栏 -->
  <div class="row bg-light py-2 mb-3">
    <div class="col-md-12 text-center">
      <div class="btn-group">
        <button class="btn btn-primary" @click="showSettings = true">设置</button>
        <button class="btn btn-secondary" @click="previewCanvas">预览</button>
        <button class="btn btn-success" @click="downloadCanvas">下载</button>
      </div>
    </div>
  </div>

  <div class="row">
    <!-- 左侧功能区域 -->
    <div class="col-md-2 sidebar">
      <h3>功能菜单</h3>
      <div class="tool btn btn-outline-dark" v-for="tool in tools" :key="tool.type" @dragstart="onDragStart(tool)" draggable="true">
        {{ tool.name }}
      </div>
    </div>

    <!-- 中间预览区域 -->
    <div class="col-md-7">
      <h4 class="preview-title">画布预览区域</h4>
      <div id="canvasContainer" 
           :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px', backgroundColor: canvasBgColor, backgroundImage: canvasBgImage ? 'url(' + canvasBgImage + ')' : 'none' }" 
           class="preview-area" 
           @dragover.prevent 
           @drop="onDrop">
        <div
          v-for="(element, index) in elements"
          :key="index"
          class="draggable-element"
          :style="{ left: element.x + 'px', top: element.y + 'px', fontSize: element.size + 'px', color: element.color, width: element.width + 'px', fontWeight: element.bold ? 'bold' : 'normal', height: element.height + 'px', borderRadius: element.borderRadius + 'px' }"
          @mousedown="onMouseDown($event, index)"
        >
          <template v-if="element.type === 'text'">
            {{ element.content }}
          </template>
          <template v-if="element.type === 'image'">
            <img :src="element.src" :style="{ width: '100%', height: '100%', borderRadius: element.borderRadius + 'px' }" />
          </template>
        </div>
      </div>
    </div>

    <!-- 右侧参数修改区域 -->
    <div class="col-md-3 bg-light py-2">
      <h4>参数设置</h4>
      <div v-if="selectedElement !== null">
        <div v-if="elements[selectedElement].type === 'text'">
          <label>文字内容:</label>
          <input v-model="elements[selectedElement].content" class="form-control"/>

          <label>文字大小:</label>
          <input type="range" v-model="elements[selectedElement].size" min="10" max="100" class="form-control"/>

          <label>文字宽度:</label>
          <input type="range" v-model="elements[selectedElement].width" min="50" max="500" class="form-control"/>

          <label>文字颜色:</label>
          <input type="color" v-model="elements[selectedElement].color" class="form-control"/>

          <label>文字加粗:</label>
          <input type="checkbox" v-model="elements[selectedElement].bold" class="form-check-input"/>
        </div>

        <div v-if="elements[selectedElement].type === 'image'">
          <label>上传图片:</label>
          <input type="file" @change="onImageUpload" class="form-control"/>

          <label>宽度:</label>
          <input type="number" v-model="elements[selectedElement].width" class="form-control"/>

          <label>高度:</label>
          <input type="number" v-model="elements[selectedElement].height" class="form-control"/>

          <label>圆角:</label>
          <input type="range" v-model="elements[selectedElement].borderRadius" min="0" max="50" class="form-control"/>
        </div>
      </div>
    </div>
  </div>

  <!-- 设置弹出层 -->
  <div v-if="showSettings" class="modal" tabindex="-1" role="dialog" style="display: block;">
    <div class="modal-dialog" role="document">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title">画布设置</h5>
          <button type="button" class="close" @click="showSettings = false">
            <span>&times;</span>
          </button>
        </div>
        <div class="modal-body">
          <label>画布宽度:</label>
          <input type="number" v-model="canvasWidth" class="form-control" />

          <label>画布高度:</label>
          <input type="number" v-model="canvasHeight" class="form-control" />

          <label>背景颜色:</label>
          <input type="color" v-model="canvasBgColor" class="form-control" />

          <label>背景图片:</label>
          <input type="file" @change="onCanvasBgUpload" class="form-control" />
        </div>
        <div class="modal-footer">
          <button type="button" class="btn btn-primary" @click="showSettings = false">保存</button>
        </div>
      </div>
    </div>
  </div>

  <!-- 预览弹出层 -->
  <div v-if="showPreview" class="modal" tabindex="-1" role="dialog" style="display: block;">
    <div class="modal-dialog modal-lg" role="document">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title">预览效果</h5>
          <button type="button" class="close" @click="showPreview = false">
            <span>&times;</span>
          </button>
        </div>
        <div class="modal-body">
          <div :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px', backgroundColor: canvasBgColor, backgroundImage: canvasBgImage ? 'url(' + canvasBgImage + ')' : 'none' }" class="preview-area">
            <div
              v-for="(element, index) in elements"
              :key="index"
              class="draggable-element"
              :style="{ left: element.x + 'px', top: element.y + 'px', fontSize: element.size + 'px', color: element.color, width: element.width + 'px', fontWeight: element.bold ? 'bold' : 'normal', height: element.height + 'px', borderRadius: element.borderRadius + 'px' }"
            >
              <template v-if="element.type === 'text'">
                {{ element.content }}
              </template>
              <template v-if="element.type === 'image'">
                <img :src="element.src" :style="{ width: '100%', height: '100%', borderRadius: element.borderRadius + 'px' }" />
              </template>
            </div>
          </div>
        </div>
        <div class="modal-footer">
          <button type="button" class="btn btn-secondary" @click="showPreview = false">关闭</button>
        </div>
      </div>
    </div>
  </div>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.min.js"></script>
<script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script>
<script>
new Vue({
  el: "#app",
  data: {
    tools: [
      { name: "文字功能", type: "text" },
      { name: "图片功能", type: "image" }
    ],
    elements: [],
    selectedElement: null,
    draggedElement: null,
    isDragging: false,
    dragOffsetX: 0,
    dragOffsetY: 0,
    showSettings: false,
    showPreview: false,
    canvasWidth: 800,
    canvasHeight: 800,
    canvasBgColor: "#ffffff",
    canvasBgImage: null
  },
  methods: {
    onDragStart(tool) {
      this.draggedElement = tool;
    },

    onDrop(event) {
      if (this.draggedElement) {
        if (this.draggedElement.type === "text") {
          this.elements.push({
            type: "text",
            content: "输入文字",
            size: 16,
            color: "#000000",
            x: event.offsetX,
            y: event.offsetY,
            width: 150,
            height: 50,
            bold: false,
            borderRadius: 0
          });
        } else if (this.draggedElement.type === "image") {
          this.elements.push({
            type: "image",
            src: "",
            x: event.offsetX,
            y: event.offsetY,
            width: 100,
            height: 100,
            borderRadius: 0
          });
        }
        this.draggedElement = null;
      }
    },

    selectElement(index) {
      this.selectedElement = index;
    },

    onImageUpload(event) {
      const file = event.target.files[0];
      const reader = new FileReader();
      reader.onload = (e) => {
        this.elements[this.selectedElement].src = e.target.result;
      };
      reader.readAsDataURL(file);
    },

    onCanvasBgUpload(event) {
      const file = event.target.files[0];
      const reader = new FileReader();
      reader.onload = (e) => {
        this.canvasBgImage = e.target.result;
      };
      reader.readAsDataURL(file);
    },

    onMouseDown(event, index) {
      this.isDragging = true;
      this.selectedElement = index;
      const element = this.elements[index];

      this.dragOffsetX = event.clientX - element.x;
      this.dragOffsetY = event.clientY - element.y;

      window.addEventListener("mousemove", this.onMouseMove);
      window.addEventListener("mouseup", this.onMouseUp);
    },

    onMouseMove(event) {
      if (this.isDragging && this.selectedElement !== null) {
        const element = this.elements[this.selectedElement];
        element.x = event.clientX - this.dragOffsetX;
        element.y = event.clientY - this.dragOffsetY;
      }
    },

    onMouseUp() {
      this.isDragging = false;
      window.removeEventListener("mousemove", this.onMouseMove);
      window.removeEventListener("mouseup", this.onMouseUp);
    },

    downloadCanvas() {
      html2canvas(document.querySelector("#canvasContainer")).then((canvas) => {
        const link = document.createElement("a");
        link.href = canvas.toDataURL();
        link.download = "canvas.png";
        link.click();
      });
    },

    previewCanvas() {
      this.showPreview = true;
    }
  }
});
</script>
</body>
</html>

images

复制全文 生成海报 Web开发 前端技术 用户界面 图形设计

推荐文章

Nginx 负载均衡
2024-11-19 10:03:14 +0800 CST
Golang在整洁架构中优雅使用事务
2024-11-18 19:26:04 +0800 CST
JavaScript设计模式:发布订阅模式
2024-11-18 01:52:39 +0800 CST
实现微信回调多域名的方法
2024-11-18 09:45:18 +0800 CST
使用Vue 3实现无刷新数据加载
2024-11-18 17:48:20 +0800 CST
Vue3中如何处理组件的单元测试?
2024-11-18 15:00:45 +0800 CST
Claude:审美炸裂的网页生成工具
2024-11-19 09:38:41 +0800 CST
html一个全屏背景视频
2024-11-18 00:48:20 +0800 CST
go错误处理
2024-11-18 18:17:38 +0800 CST
什么是Vue实例(Vue Instance)?
2024-11-19 06:04:20 +0800 CST
程序员出海搞钱工具库
2024-11-18 22:16:19 +0800 CST
php strpos查找字符串性能对比
2024-11-19 08:15:16 +0800 CST
Vue 3 路由守卫详解与实战
2024-11-17 04:39:17 +0800 CST
IP地址获取函数
2024-11-19 00:03:29 +0800 CST
在 Docker 中部署 Vue 开发环境
2024-11-18 15:04:41 +0800 CST
2025,重新认识 HTML!
2025-02-07 14:40:00 +0800 CST
15 个你应该了解的有用 CSS 属性
2024-11-18 15:24:50 +0800 CST
Vue3 组件间通信的多种方式
2024-11-19 02:57:47 +0800 CST
js常用通用函数
2024-11-17 05:57:52 +0800 CST
Nginx 防止IP伪造,绕过IP限制
2025-01-15 09:44:42 +0800 CST
Rust 与 sqlx:数据库迁移实战指南
2024-11-19 02:38:49 +0800 CST
回到上次阅读位置技术实践
2025-04-19 09:47:31 +0800 CST
程序员茄子在线接单