vue3使用ResizeObserver 实现tag响应式展示

vue3、 ant-design-vue

需求: 实现一个 tag 标签,标签内容随宽度,动态展示,标签内容超出就正常显示,多出的tag,用 +n 表示,hover时,tooltip显示剩余tag。

1、能否纯css能否实现

 使用flex + flex-wrap: wrap + overflow:hidden;

如图

 

显然不满足,找到 ResizeObserver 可实现宽度监听

思路:通过ResizeObserver接口获取到父元素变化及子元素的宽度,结合父元素宽度及子元素累计宽度,动态计算宽度。超出的子元素 设置 隐藏。

  想到 ant-design-vue中的 多选下拉框属于类似功能。

找到源码文件  ant-design-vue\components\vc-resize-observer\index.tsx 也是使用了ResizeObserver Api

onresize函数就能返回元素宽高,直接使用该组件。

新建一个vue文件,存放父元素及子元素

套上ResizeObserver组件

ResizeObserver 在组件渲染时会触发一次,所以将子元素的宽度保存起来

当父元素宽度变化时,调用自定义的判断超出

这里已经可以实现响应式设置了。

但在,新增子标签的情况下,不会刷新,因为parent组件宽度没变化,onResizeOut,不会被触发。所以需要监听 tagList变化,同时设置父元素 v-if 触发强制更新。

到这里,就可以实现tag响应式展示。

全部代码

使用

//引用 
import overflowTag from './overflow-tag.vue';
const list = ref([{label:'11111'},{label:'22222'}]);




<overflowTag :tag-list="List" />

 overflow-tag.vue

<script setup lang="ts">
import { onBeforeMount, reactive, ref, computed, watch, nextTick } from 'vue';
import ResizeObserver from 'ant-design-vue/es/vc-resize-observer';
import Item from './item.vue';
interface Size {
    width: number;
    height: number;
    offsetWidth: number;
    offsetHeight: number;
}

const props = defineProps({
    tagList: {
        type: Array,
        default: () => [],
    },
});
const orderList = ref<any>([]);
onBeforeMount(() => {
    setOrderList();
});
const setOrderList = () => {
    orderList.value = props.tagList.map((item: any) => ({
        ...item,
        size: { height: 0, offsetWidth: 0 },
        disabled: false,
    }));
};
const more = ref({
    count: 0,
    disabled: true,
    order: 0,
    size: {
        offsetWidth: 0,
    } as Size,
    text: '',
});
const style = computed(() => {
    if (more.value.disabled) {
        return {
            opacity: 0,
            height: '0px',
            'overflow-y': 'hidden',
            'pointer-events': 'none',
            position: 'absolute',
        };
    }
    return {
        opacity: 1,
        height: '24px',
    };
});
const content = ref({
    size: {
        width: 0,
    } as Size,
});
const resizeObserverDisabled = computed(() => {
    return !props.tagList.length;
});
const onResize = (size: Size, index: number) => {
    orderList.value[index].size = size;
};
const onResizeMore = (size: Size) => {
    more.value.size = size;
};
const onResizeOut = (size: Size) => {
    content.value.size = size;
    const contentWidth = size.offsetWidth;
    let maxWidth = 0;
    const len = orderList.value.length;
    more.value.count = 0;
    more.value.disabled = true;
    more.value.order = 0;
    more.value.text = '';
    for (let i = 0; i < len; i++) {
        const item = orderList.value[i];
        let nowWidth = item.size.offsetWidth + 8;
        maxWidth += nowWidth;
        if (maxWidth < contentWidth) {
            item.disabled = false;
        } else {
            item.disabled = true;
            if (!more.value.count) {
                const moreLen =
                    maxWidth - nowWidth + more.value.size.offsetWidth;
                if (moreLen > contentWidth - 8) {
                    more.value.count = len - i + 1;
                    orderList.value[i - 1].disabled = true;
                    more.value.order = i - 1;
                } else {
                    more.value.count = len - i;
                    more.value.order = i;
                }
                more.value.text = props.tagList
                    .map((item: any) => item.label)
                    .splice(more.value.order, more.value.count)
                    .join(',');
                more.value.disabled = false;
            }
        }
    }
};
watch(
    () => props.tagList,
    (value) => {
        orderList.value = [];
        nextTick(() => {
            orderList.value = value.map((item: any) => ({
                ...item,
                size: { height: 0, offsetWidth: 0 },
                disabled: false,
            }));
        });
    },
);
</script>

<template>
    <ResizeObserver :disabled="resizeObserverDisabled" @resize="onResizeOut">
        <div v-if="orderList.length" class="overflow-tag">
            <a-tooltip :title="more.text">
                <ResizeObserver
                    :disabled="resizeObserverDisabled"
                    @resize="onResizeMore"
                >
                    <div
                        class="overflow-tag-item overflow-tag-item-more"
                        :style="{
                            ...style,
                            order: more.order,
                        }"
                    >
                        +{{ more.count }}
                    </div>
                </ResizeObserver>
            </a-tooltip>
            <template v-for="(item, index) in orderList" :key="item.label">
                <ResizeObserver
                    :disabled="resizeObserverDisabled"
                    @resize="(size) => onResize(size, index)"
                >
                    <Item :item="item" :order="index" />
                </ResizeObserver>
            </template>
        </div>
    </ResizeObserver>
</template>
<style scoped lang="less">
.overflow-tag {
    width: 100%;
    display: flex;
    flex-wrap: wrap;
    overflow: hidden;
    position: relative;
    .overflow-tag-item {
        background: #f5f5f5;
        border: 1px solid #f0f0f0;
        color: #111;
        border-radius: 2px;
        width: auto;
        padding: 2px 6px;
        margin-right: 8px;
        flex: none;
        align-self: center;
        max-width: 100%;
        &:last-of-type {
            margin-right: 0px;
        }
    }
    .overflow-tag-item-more {
        margin-right: 0;
    }
}
</style>

item.vue

<script setup lang="ts">
import { computed, onBeforeMount, reactive, ref } from 'vue';
const props = defineProps({
    item: {
        type: Object,
        default: () => ({
            disabled: false,
            label: '',
        }),
    },
    order: {
        type: Number,
        default: 0,
    },
});
const style = computed(() => {
    if (props.item.disabled) {
        return {
            opacity: 0,
            height: '0px',
            'overflow-y': 'hidden',
            'pointer-events': 'none',
            position: 'absolute',
        };
    }
    return {
        opacity: 1,
        height: '24px',
    };
});
</script>

<template>
    <div
        class="overflow-tag-item"
        :style="{
            order: order,
            ...style,
        }"
    >
        {{ item.label }}
    </div>
</template>
<style scoped lang="less"></style>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值