VueJS не обновляет стили элемента

90
01 марта 2021, 16:10

Пишу drag-and-drop с возможностью поворота и ресайза на VueJs. Суть работы простая: отслеживаю перемещения мышкой, в соответствии с ними обновляю модель, в которой хранится положение элемента и которая потом отправляется на сервер. Эта модель успешно обновляется при перемещениях мышки, но стили, которые я к ней прикрепил, не обновляются.

<div
    :style="{
        top: layout.top,
        left: layout.left,
        width: layout.width,
        rotate: layout.rotate,
        position: 'absolute'}"
 >

console.log при этом пишет, что модель обновляется. Что я делаю не так? В чём может быть причина? Заранее спасибо PS. Попробовал вывести в консоли модель при перемещении мышью - всё норм. Выводит это:

Но watch его не видит (точнее, не видит при перемещении, хотя видит при вставке фото в макет)(на проверку if(this.pageKey === 'photo_cover_main') не обращайте внимания):

layout: function (val) {
    if(this.pageKey === 'photo_cover_main')
        alert()
}

Соответственно, из-за этого не перемещается сама фотка. Почему даже watch не видит - не понимаю Модель передаётся в этот компонент свойством, а при перемещении фото вызывается $emit на родителя, который обновляет данные

Layout получается запросом к API

$.get('/api/users/order-layout', data => {
     this.layout = data;
});

В родительском компоненте следующий код шаблона:

<hole-view
    v-for="(hole, holeKey) in page.holes_coordinats"
    :hole="hole"
    :hole-key="holeKey"
    :layout="layout.content[pageKey] && layout.content[pageKey][holeKey] ? layout.content[pageKey][holeKey] : {}"
    :page-key="pageKey"
    @moved="movedHoleImageBind"

/>

В дочернем компоненте при перетаскивании объекта мышью после вычисления всех координат вызывается $emit:

this.$emit('moved', top, left, x, y);

В родительском компоненте на событие moved вешается такой обработчик:

movedHoleImageBind(top, left, x, y){
            try{
                this.$set(this.layout.content[this.editingHoleImage.pageKey][this.editingHoleImage.holeKey], 'top', top);
                this.$set(this.layout.content[this.editingHoleImage.pageKey][this.editingHoleImage.holeKey], 'left', left);
            }catch (e) {}
        }

HTML получается такой (сама фотка - div.image-in-layout-slider и img в нём):

<div data-v-1a44c9e7="" data-v-58dc894e="" data-index="0" data-page="photo_cover_main"
 class="slider__empty-block slider__empty-block-focus"
 style="top: 19.6875%; left: 64.3931%; width: 22.7381%; height: 50%; position: absolute;">
<div data-v-1a44c9e7="" class="image-in-layout-slider"
     style="top: -22.4529%; left: 0px; width: 100%; position: absolute;"><img data-v-1a44c9e7=""
                                                                              src="http://deti/wp-content/uploads/general_photos/83/FdjjGawbNjA.jpg"
                                                                              class="img-responsive ls-is-cached lazyloaded"><span
            data-v-1a44c9e7="" title="Повернуть" class="rotate-slider-image"><i data-v-1a44c9e7=""
                                                                                aria-hidden="true"
                                                                                class="glyphicon glyphicon-repeat"></i></span><span
            data-v-1a44c9e7="" title="Удалить" class="slider-photo-remove-js"><i data-v-1a44c9e7=""
                                                                                 aria-hidden="true"
                                                                                 class="glyphicon glyphicon-remove"></i></span>
    <div data-v-1a44c9e7="" class="my-resizable-block my-resizable-block-vertical"></div>
    <div data-v-1a44c9e7="" class="my-resizable-block my-resizable-block-horizontal"></div>
    <div data-v-1a44c9e7="" class="my-resizable-block my-resizable-block-diagonal"></div>
</div>

Answer 1

Убедитесь, что дочерний компонент отправляет необходимые данные:

console.log({top, left, x, y});
this.$emit('moved', top, left, x, y);

Убедитесь, что родительский компонент принимает их, а также, что свойства top и left изначально были прописаны в объектах:

movedHoleImageBind(top, left, x, y){
  console.log({top, left, x, y});
  
  this.layout.content[this.editingHoleImage.pageKey][this.editingHoleImage.holeKey].top = top;
  this.layout.content[this.editingHoleImage.pageKey][this.editingHoleImage.holeKey].left = left;
}

Общие рекомендации

1 Возможно, что вы не передаёте единицы измерения и обращаетесь к не существующему CSS свойству rotate. Как бы там ни было, свойства лучше перенести в поле computed.

// Отключим ненужные для примера
// сообщения в консоли.
Vue.config.productionTip = false;
Vue.config.devtools = false;
// Дочерний компонент.
const HoleView = {
  props: ['layout'],
  computed: {
    dragStyle() {
      return {
        'top': this.layout.top + 'px',
        'left': this.layout.left + 'px',
        'width': this.layout.width + 'px',
        'transform': 'rotate(' + this.layout.rotate + 'deg)'
      }
    }
  },
  template: `<div class="block" :style="dragStyle">Содержимое блока</div>`
}
new Vue({
  el: '#app',
  components: {
    'hole-view': HoleView
  },
  data: {
    layout: {
      top: 0,
      left: 0,
      width: 288,
      rotate: 0
    },
  }
});
.container {
  max-width: 800px;
  margin: 50px auto;
  position: relative;
}
.form {
  max-width: 800px;
  margin: 50px auto;
  position: relative;
  display: flex;
  flex-wrap: wrap;
}
.label {
  display: block;
}
.block {
  height: 188px;
  box-sizing: border-box;
  border: 1px dashed #888;
  background: rgb(178, 214, 60);
  position: absolute;
}
<div id="app">
  <form class="form">
    <div>
      <label class="label">Слева {{ layout.left }}</label>
      <input type="range" v-model="layout.left">
    </div>
    <div>
      <label class="label">Сверху {{ layout.top }}</label>
      <input type="range" v-model="layout.top">
    </div>
    <div>
      <label class="label">Ширина {{ layout.width }}</label>
      <input type="range" v-model="layout.width" min="120" max="400">
    </div>
    <div>
      <label class="label">Поворот {{ layout.rotate }}</label>
      <input type="range" v-model="layout.rotate" min="0" max="360">
    </div>
  </form>
  <div class="container">
    <hole-view :layout="layout"></hole-view>
  </div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

2 Укажите уникальный атрибут key для каждого элемента, вот так:

<hole-view v-for="(hole, holeKey) in page.holes_coordinats" :key="holeKey" ...>

3 Указывайте глубину слежения за объектом в поле watch:

// ... остальной код
watch: {
  // Коллбэк будет вызываться каждый раз,
  // когда изменяется любое из свойств наблюдаемого объекта.
  layout: {
    handler: 'layoutWatcher',
    deep: true, // независимо от глубины их вложенности.
    immediate: true // коллбэк будет вызван сразу же после начала наблюдения.
  },
}
// ... остальной код
methods: {
  layoutWatcher(newVal, oldVal) {
    // Реализация метода для наблюдателя.
    // ... code
  }
}
READ ALSO
Как имитировать ввод текста в input password

Как имитировать ввод текста в input password

Есть сайт на angular, сайт не мойХочу немного оптимизировать работу с ним для себя

96
В чём разница между querySelector и getElementById?

В чём разница между querySelector и getElementById?

Вопрос, возможно, слишком общий или не по теме, но мне интересноЯ видел, что кто-то использует querySelector для поиска элемента по id, а не по имени...

106
Discord JS: Не могу изменить имя бота

Discord JS: Не могу изменить имя бота

Хочу сделать, чтобы бот менял свой никПодскажите как правильно это сделать

127