简体   繁体   中英

Updating CSS classes in on div in Vue3 component, depending on props, does not work (Missing reactivity?)

I found a good looking bottom navigation bar for vue on Github ( https://github.com/imanmalekian31/vue-bottom-navigation ).

Sadly it did not work with Vue3, Typescript and the script setup syntax.

I rewrote the component and got it to run and show up in my app.

The problem is, the buttons are not reactive at the moment. While the methods on click get run, the css classes on the elements do not get updated.

My best guess here is, that localOptions and button in

<div v-for="(button, index) in localOptions" :key="`grow-button-${index}`"></div>

need to be reactive, which they might not due to the change to script setup.

Any help on how to fix this code would be greatly appreciated.

Update: I did replace let localOptions: any[] = reactive([]) with let localOptions = reactive(props.options.slice()) and that seems to have done the trick. The buttons are working and updating just fine.

The problem that appeared now is, that the computed Property doesn't seem to be run every time the component re-renders. Therefore the buttons are not changing width or color.

Component file:

<template>
  <div class="gr-btn-container-foreground" :style="cssVariables">
    <div v-for="(button, index) in localOptions" :key="`grow-button-${index}`" :class="[
      'gr-btn-container',
      { 'gr-btn-container-active': button.selected },
    ]" @click="handleButtonClick(button, index)">
      <div :class="['gr-btn-item', { 'gr-btn-item-active': button.selected }]">
        <div :class="['gr-btn-icon', { 'gr-btn-icon-active': button.selected }]">
          <slot name="icon" :props="button">
            <i :class="`${button.icon}`" />
          </slot>
        </div>
        <div class="gr-btn-title">
          <span class="gr-hidden-title">
            <slot name="title" :props="button">{{ button.title }}</slot>
          </span>
          <span :class="[
            'gr-animated-title',
            { 'gr-animated-title-active': button.selected },
          ]">
            <slot name="title" :props="button">{{ button.title }}</slot>
          </span>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed, reactive } from "vue";
import { useRoute, useRouter } from 'vue-router'

const router = useRouter()
const route = useRoute()
const button = reactive({})


const model = {
  prop: "value",
  event: "update",
}
const props = defineProps({
  value: {
    default: null,
  },
  options: {
    type: Array,
    default: () => [],
  },
  color: {
    type: String,
    default: "#74cbbb",
  },
  replaceRoute: {
    type: Boolean,
    default: false,
  },
})

const emit = defineEmits<{
  (e: 'update', value: string): void
}>()

let prevSelected = null
let currSelected = null
let localOptions: any[] = reactive([])

const cssVariables = computed(() => {

  const activeTitle = (localOptions[currSelected] || {}).title;
  let activeWidth = 95;
  if (activeTitle && activeTitle.length * 15 > 110) {
    activeWidth = 95 + (activeTitle.length * 15 - 110) / 2;
  }

  const mainColor =
    (localOptions[currSelected] || {}).color || props.color;

  const styles = {
    "--color": mainColor,
    "--color-background": mainColor + "30",
    "--active-width": `${activeWidth}px`,
  };

  return styles;
})

localOptions = props.options.slice();

let index = localOptions.findIndex(
  (item) =>
    item.id == props.value ||
    (item.path || {}).name == (route || {}).name
);

if (index > -1) {
  currSelected = index;
  prevSelected = index;

  localOptions[index] = { ...localOptions[index], selected: true }
}


function handleButtonClick(button: any, index: number) {
  console.log("Button: " + button + " Index: " + index);
  currSelected = index;

  if (prevSelected !== null) {
    localOptions[prevSelected].selected = false;
  }

  localOptions[index] = { ...localOptions[index], selected: true }

  prevSelected = currSelected;
  updateValue(button);
}

function updateValue(button) {
  console.log("Update value: " + button.id + " with Path: " + button.path)

  emit("update", button.id);

  if (button.path && Object.keys(button.path).length) {
    router[!props.replaceRoute ? "push" : "replace"](
      button.path
    ).catch(() => { console.log("Error updating path") });
  }
}

</script>

<style scoped>
.gr-btn-container-foreground {
  position: fixed;
  direction: ltr;
  display: flex;
  align-items: center;
  bottom: 0;
  width: 100%;
  z-index: 2147483647;
  height: 64px;
  background: #fff;
  box-shadow: 0 0 5px 0 #eee;
}

.gr-btn-container {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 45px;
  flex-grow: 1;
  transition: all 0.3s;
}

@media (min-width: 576px) {
  .gr-btn-container {
    cursor: pointer;
  }
}

.gr-btn-container-active {
  background-color: var(--color-background);
  border-radius: 100px;
}

.gr-btn-item {
  display: flex;
  position: relative;
  overflow: hidden;
  width: 24px;
  transition: all 0.3s ease;
  color: #0000008a;
}

.gr-btn-item-active {
  width: var(--active-width);
}

.gr-btn-icon {
  font-size: 20px;
  transition: all 0.3s ease;
  margin: 0px !important;
}

.gr-btn-icon-active {
  color: var(--color);
}

.gr-btn-title {
  position: relative;
  color: var(--color);
  padding: 0px 5px;
  margin-left: 10px;
  display: flex;
  justify-content: center;
  align-items: center;
}

.gr-hidden-title {
  visibility: hidden;
}

.gr-animated-title {
  color: var(--color);
  position: absolute;
  left: 5px;
  bottom: -15px;
  transition: bottom 0.4s ease 0.1s;
}

.gr-animated-title-active {
  bottom: 2px;
}
</style>

currSelected is not a reactive variable (ref). As far as I quickly see this is the reason why your cssVariables computed does not recalculate - it misses reactive dependence of currSelected

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM