How to flexibly realize the expansion and collapse function of text or pictures?

Achieve such a requirement: There is a product details page that needs to display product description content. There are three types of content, plain text, pure pictures, and text plus pictures. The display area has a fixed height. If the content exceeds this height, an expand button is displayed. , click the expand button to expand more content; if the content does not exceed this height, the expand button will not be displayed

Implementation ideas:
First, it is necessary to determine whether the expand button is displayed based on whether the height of the content exceeds the specified height, so you need to first obtain the DOM element and the height of the element. Since page rendering takes time, a delay needs to be added here to obtain the height of the element; and then because Image rendering takes longer than text, especially when there are many images. Therefore, when determining that the content contains images, you need to obtain the height of the element in the onload event of the image, that is, after the image rendering is completed. Then get the height of the element; because after adding the delay, the picture will first expand the whole thing and then suddenly be retracted, which makes the user experience very bad, so a skeleton screen is added here to hide the rendering process. Effect

The specific code is as follows:

Parent component:

<template>
<div class="merchant-wrap">
<div v-if="isShowSkeleton>
<van-skeleton :row="5" round>
<van-skeleton :row="5" round>
<van-skeleton :row="5" round>
</div>
<div class="merchant-reminds" v-if="!isShowSkeleton">
<div class="merchant-reminds-wrap">
<div class="reminds-header" id="tabs0">
<goods :data="data.detailsInfo" :isDetail="true"" >
</div>
</div>
</div>
</template>
import {<!-- -->ref,reactive} from 'vue'
import Goods from './components/goods . vue'
import {<!-- --> goodsDetail ] from '@/api/modules/good.js'

export default {<!-- -->
setup(){<!-- -->
const isShowSkeleton = ref(true)
const data = reactive({<!-- -->
detailsInfo:{<!-- -->}
})
const getGoodsDetall = () => {<!-- -->
isShowSkeleton.value = true
goodsDetail({<!-- --> goodsId: route.query.goodsId })
then(res => {<!-- -->
if (res) {<!-- -->
if (res .code == 20000) {<!-- -->
isShowSkeleton.value = false
data.detailsInfo = res.data.goods
}
}
})
.catch(error => {<!-- -->})
}
const init = () => {<!-- -->
getGoodsDetall()
}
init()
return {<!-- -->
isShowSkeleton
}
},
components: {<!-- -->
Goods
}
}

Subcomponent

<template>
<div v-if="isShowMask>
<van-skeleton :row="5" round>
<van-skeleton :row="5" round>
<van-skeleton :row="5" round>
</div>
<div class="content">
<div class="goods-picture" v-if="data.des">
<div :class="[state.isRulessshow ? 'img-detail':'img-content']" class="goods-picture-content">
<div class="content-wrap" v-for="(item, index) in data.des" :key="index">
// render text
<div>
<div class="item-text" v-if="item.type == 0" v-html="item.value"></div>
</div>
// Render image
<div v-if="item.type == 1 & amp; & amp; item.value">
<div v-for="(ele, index) in item.value.split(",")" :key="index">
<img @load="loadImage" :src="ele" alt="" />
</div>
</div>
</div>
</div>
</div>
<div v-if="isShowMore" class="look-all" @click="switchRules">
<div class="rules-words">{<!-- -->{ rulesWords }}</div>
<div class="arrow_down">
<img class="arrow" src="@/assets/iconsvg/arrowDown.svg" :class="{ active:state.isRulesshow}" alt="" />
</div>
</div>
</div>
</template>
import {<!-- -->ref,reactive,computed,nextTick} from 'vue'

export default {<!-- -->
props: {<!-- -->
data: {<!-- -->
type: Object,
default: () => ({<!-- -->}),
}
},
setup(props){<!-- -->
const isShowMask = ref(true)
const isShowMore = ref(false)
const state = reactive({<!-- -->
isRule:{<!-- -->}
})
const loadImage = () => {<!-- -->
console.log('Image loading completed')
getTextHeight()
}
const getTextHeight = () => {<!-- -->
console.log(11111)
const contentDiv = document.querySelectorAl1('.content-wrap')
let divHeight = 0
if (contentDiv & amp; & amp; contentDiv.length > 0){<!-- -->
contentDiv.forEach(item => {<!-- -->
divHeight + = item.offsetHeight
})
console.log(divHeight,---- --- ---divHeight")
if (divHeight > 150) {<!-- -->
isShowMore.value = true
setTimeout(() => {<!-- -->
isShowMask.value = false
},500)
} else {<!-- -->
isShowMask.value = false
}
} else {<!-- -->
isShowMask.value = false
}
},
const init = () => {<!-- -->
if (props .data .des ) {<!-- -->
let imgData = props.data.des.filter(item => {<!-- -->
return item.type == '1'
})
let textData = props.data.des.filter(item => {<!-- -->
return item.type == '0'
})
if (imgData.length || textData.length > 0) {<!-- -->
if (imgData.length > 0) {<!-- -->
loadImage()
} else {<!-- -->
setTimeout(() => {<!-- -->
getTextHeight()
}, 400)
}
} else {<!-- -->
isShowMask.value = false
}
} else
isShowMask.value = false
}
},
const rulesWords = computed(()
if (state.isRulesShow === false) {<!-- -->
return lang.value == 'more'
} else if (state.isRulesShow === true) {<!-- -->
return lang.value == 'Collapse'
} else if (state.isRulesshow === '') {<!-- -->
return null
} else {<!-- -->
return null
}
})
const switchRules = () => (
state.isRuleShow = !state.isRulesShow
}
onMounted(() => {<!-- -->
nextTick(() => {<!-- -->
init()
})
})
return {<!-- -->
isShowMask,
isShowMore,
rulesWords,
switchRules
}
},
}
<style lang="scss" scoped>
.mask-skeleton {<!-- -->
min-height: calc(100vh + 1px);
padding-top: 1px;
--van-skeleton-row-background-color:#fff;
background: rgb(243, 245, 252);
position: fixed;
width: 100%;
height: 100%;
left: 0;
top: 0;
z-index: 999;
:deep(.van-skeleton content) {<!-- -->
margin-top: 20px;
}
:deep( .van-skeleton row) {<!-- -->
height: 20px;
}
}
.content {<!-- -->
.goods-picture {<!-- -->
border-top: 1px solid #ele1e1;
margin-top: 18px;
.img-content {<!-- -->
max-height: 150px;
overflow: hidden;
}
.img-detail {<!-- -->
transition: height 2s linear;
}
.look-all {<!-- -->
color: #001a93;
margin-top: 10px;
font-size: 16px;
display: flex;
align-items: center;
justify-content: flex-end;
.rules-words {<!-- -->
margin-right: 5px;
}
.arrow_down {<!-- -->
width: 12px;
height: 12px;
line-height: 12px;
.arrow {<!-- -->
width: 100%;
height: 100%;
font-size: 0:
& amp;.active {<!-- -->
transform: rotate(180deg);
}
}
}
}
.goods-picture-content{<!-- -->
margin-top: 16px;
font-size: 14px;
img {<!-- -->
width: 100%;
}
}
}
}
\t
</style>