Thomas
Thomas

Reputation: 7078

Which event do I need to capture to catching a mouse move while mousedown with Vue

I'm building a 2D game with a top down map using Vue3. I created a simple map editor, where you can drag each tile from a set to the map. This can get very cumbersome for a large map, thus I want to try another way. My idea is like a drawing tool, you select the "brush" and drag it over a "canvas". The only problem is, the map is built of tile div arranged in a matrix. I tried the following, but for some reason the drawing is delayed and it generates "artifacts" on other tiles (I start dragging on tile 20:4 but also tile 15:2 is updated.

<div class="tiles">
    <div v-for="(row, y) in tiles" class="row"
        :key="'row_' + y"
        :style="{width: mapSize.x + 'px'}">
        <div v-for="(tile, x) in row" class="tile"
            :class="tile.background"
            :key="'tile_' + x + '_' + y"
            @dragover.prevent
            @dragenter="onDragEnter(x, y)">
        </div>
    </div>
</div>

I don't have a draggable element, maybe this is the problem, but what other event could I use for a clicked move?

Upvotes: 0

Views: 1169

Answers (1)

Daniel
Daniel

Reputation: 35684

I would suggest that you use mousedown, mousemove, and mouseup and possibly their touch semi-equivalents (if using touch devices).

Here is an example, with comments in the code.

const colors = ["powderblue","gold","orangered","deeppink", "deepskyblue","palegreen"];

const generateTiles = (wx, wy) => {
  const ret = [];
  for (let y = 0; y < wy; y++) {
    const row = [];
    ret.push(row)
    for (let x = 0; x < wx; x++) {
      row.push({x,y,background: colors[0]})
    }
  }
  return ret;
}

Vue.createApp({
  el: '#app',
  setup() {
    const tiles = Vue.reactive(generateTiles(10,10)); // setup tiles and make reactive
    const color = Vue.ref(1); // helper for managing color(s)
    let mouseDown = false; // no need to make reactive
    
    // do some stuff to the tile here
    const setTileBg = (tile) => {
        tile.background=colors[color.value];
    }
    
    const handlers = {
        // reuse handler, used both for parent and 
      onMouseDown: (tile) => {
        mouseDown = true;
        if(tile) setTileBg(tile);
      },
      onMouseUp: (e) => {
        mouseDown = false;
      },
      onMouseOver: (tile) => {
        if(mouseDown) setTileBg(tile);
      }
    }
    
    // adding it to window (instead of an element) allows
    // capturing mouseup event even when mouse is not within
    // the dom element
    window.addEventListener('mouseup', handlers.onMouseUp)
    

    return {
        tiles, color, colors, ...handlers
    }
  }
}).mount('#app')
.tiles,select{width:200px;margin:0 auto;display:block}.tile{display:inline-block;width:20px;height:20px;border:1px solid #000;box-sizing:border-box;font-size:10px;font-family:monospace;cursor:pointer;color:rgba(0,0,0,.4);text-align:center;padding:3px}.row{display:block;height:20px;width:200px;box-sizing:content-box;user-select:none}
<script src="https://unpkg.com/[email protected]/dist/vue.global.prod.js"></script>
<div id="app">
  <div>
    <select v-model="color">
      <option :value="i" v-for="(c,i) in colors" :style="{'background-color':c}">{{c}}</option>
    </select>
  </div>
  
  <div class="tiles">
      <div v-for="(row, y) in tiles" class="row"
          :key="'row_' + y"
          >
          <div v-for="(tile, x) in row" class="tile"
              :style="{'background-color':tile.background}"
              :key="'tile_' + x + '_' + y"
              @mouseDown="onMouseDown(tile)"
              @mouseOver="onMouseOver(tile)">
              {{ y * row.length + x}}
          </div>
      </div>
  </div>
</div>

Upvotes: 1

Related Questions