fix(bookDetail): 优化书籍详情页简介展开折叠检测逻辑
改用ResizeObserver替代原有的setTimeout与hasEllipsis检测方式,实现更精准的文本溢出判断;添加书籍简介内容变化监听,同步更新展开状态;在组件失活与卸载时正确清理观察者与动画帧计时器。
Showing
1 changed file
with
84 additions
and
10 deletions
| ... | @@ -16,7 +16,7 @@ | ... | @@ -16,7 +16,7 @@ |
| 16 | </div> | 16 | </div> |
| 17 | <div class="book-intro"> | 17 | <div class="book-intro"> |
| 18 | <p class="book-post">{{ bookInfo.name }}</p> | 18 | <p class="book-post">{{ bookInfo.name }}</p> |
| 19 | - <div id="book-intro" :class="{ 'van-multi-ellipsis--l3': isToggle }" v-html="bookInfo.note"></div> | 19 | + <div id="book-intro" ref="bookIntroRef" :class="{ 'van-multi-ellipsis--l3': isToggle }" v-html="bookInfo.note"></div> |
| 20 | <template v-if="hasToggle"> | 20 | <template v-if="hasToggle"> |
| 21 | <div v-if="isToggle" class="book-toggle-icon" @click="isToggle = false"> | 21 | <div v-if="isToggle" class="book-toggle-icon" @click="isToggle = false"> |
| 22 | 展开 | 22 | 展开 |
| ... | @@ -121,9 +121,9 @@ | ... | @@ -121,9 +121,9 @@ |
| 121 | </template> | 121 | </template> |
| 122 | 122 | ||
| 123 | <script setup> | 123 | <script setup> |
| 124 | -import { ref, onActivated, onMounted } from 'vue' | 124 | +import { ref, watch, nextTick, onActivated, onMounted, onDeactivated, onBeforeUnmount } from 'vue' |
| 125 | import { useRoute, useRouter, onBeforeRouteLeave } from 'vue-router' | 125 | import { useRoute, useRouter, onBeforeRouteLeave } from 'vue-router' |
| 126 | -import { _, storeToRefs, mainStore, showSuccessToast, hasEllipsis } from '@/utils/generatePackage' | 126 | +import { _, storeToRefs, mainStore, showSuccessToast } from '@/utils/generatePackage' |
| 127 | import { MyButton, VideoCard, NoticeOverlayModule, DonateFlower, ShortcutFixed } from '@/utils/generateModules' | 127 | import { MyButton, VideoCard, NoticeOverlayModule, DonateFlower, ShortcutFixed } from '@/utils/generateModules' |
| 128 | import { icon_video, icon_up, icon_down, icon_subscribed, icon_unsubscribe, no_image } from '@/utils/generateIcons' | 128 | import { icon_video, icon_up, icon_down, icon_subscribed, icon_unsubscribe, no_image } from '@/utils/generateIcons' |
| 129 | import { JSJ_FORM_MANDARIN, JSJ_FORM_LOCALISM } from '@/constant' | 129 | import { JSJ_FORM_MANDARIN, JSJ_FORM_LOCALISM } from '@/constant' |
| ... | @@ -157,13 +157,76 @@ const customStyle = ref({ | ... | @@ -157,13 +157,76 @@ const customStyle = ref({ |
| 157 | }) | 157 | }) |
| 158 | 158 | ||
| 159 | const donateInfo = ref({}) | 159 | const donateInfo = ref({}) |
| 160 | +const bookIntroRef = ref(null) | ||
| 161 | + | ||
| 162 | +const hasToggle = ref(false); // 判断是否有展开文字,默认没有 | ||
| 163 | +const isToggle = ref(true); // 判断展开状态,默认展开 | ||
| 164 | + | ||
| 165 | +let introResizeObserver = null | ||
| 166 | +let introMeasureTimer = null | ||
| 167 | + | ||
| 168 | +const getIntroToggleStatus = () => { | ||
| 169 | + const el = bookIntroRef.value; | ||
| 170 | + if (!el || !el.clientWidth) { | ||
| 171 | + return false; | ||
| 172 | + } | ||
| 173 | + | ||
| 174 | + const clone = el.cloneNode(true); | ||
| 175 | + clone.removeAttribute('id'); | ||
| 176 | + clone.style.position = 'fixed'; | ||
| 177 | + clone.style.left = '-99999px'; | ||
| 178 | + clone.style.top = '-99999px'; | ||
| 179 | + clone.style.zIndex = '-1'; | ||
| 180 | + clone.style.visibility = 'hidden'; | ||
| 181 | + clone.style.pointerEvents = 'none'; | ||
| 182 | + clone.style.height = 'auto'; | ||
| 183 | + clone.style.maxHeight = 'none'; | ||
| 184 | + clone.style.width = `${el.clientWidth}px`; | ||
| 185 | + clone.classList.add('van-multi-ellipsis--l3'); | ||
| 186 | + | ||
| 187 | + document.body.appendChild(clone); | ||
| 188 | + const flag = clone.scrollHeight > clone.clientHeight; | ||
| 189 | + document.body.removeChild(clone); | ||
| 190 | + | ||
| 191 | + return flag; | ||
| 192 | +} | ||
| 193 | + | ||
| 194 | +const updateHasToggle = async () => { | ||
| 195 | + await nextTick(); | ||
| 196 | + if (introMeasureTimer) { | ||
| 197 | + cancelAnimationFrame(introMeasureTimer); | ||
| 198 | + } | ||
| 199 | + introMeasureTimer = requestAnimationFrame(() => { | ||
| 200 | + hasToggle.value = getIntroToggleStatus(); | ||
| 201 | + }); | ||
| 202 | +} | ||
| 203 | + | ||
| 204 | +const bindIntroObserver = () => { | ||
| 205 | + if (!window.ResizeObserver || introResizeObserver || !bookIntroRef.value) return; | ||
| 206 | + | ||
| 207 | + introResizeObserver = new ResizeObserver(() => { | ||
| 208 | + updateHasToggle(); | ||
| 209 | + }); | ||
| 210 | + introResizeObserver.observe(bookIntroRef.value); | ||
| 211 | +} | ||
| 212 | + | ||
| 213 | +const clearIntroObserver = () => { | ||
| 214 | + if (introResizeObserver) { | ||
| 215 | + introResizeObserver.disconnect(); | ||
| 216 | + introResizeObserver = null; | ||
| 217 | + } | ||
| 218 | + | ||
| 219 | + if (introMeasureTimer) { | ||
| 220 | + cancelAnimationFrame(introMeasureTimer); | ||
| 221 | + introMeasureTimer = null; | ||
| 222 | + } | ||
| 223 | +} | ||
| 224 | + | ||
| 160 | onMounted(async () => { | 225 | onMounted(async () => { |
| 161 | const { data } = await prepareDonateAPI(); | 226 | const { data } = await prepareDonateAPI(); |
| 162 | donateInfo.value = data; | 227 | donateInfo.value = data; |
| 163 | - setTimeout(() => { | 228 | + bindIntroObserver(); |
| 164 | - // 判断是否显示简介的展开图标 | 229 | + updateHasToggle(); |
| 165 | - hasToggle.value = hasEllipsis(`book-intro`); | ||
| 166 | - }, 500); | ||
| 167 | // TAG: 监听滚动到底部 | 230 | // TAG: 监听滚动到底部 |
| 168 | // 快捷访问组件高度动态调整 | 231 | // 快捷访问组件高度动态调整 |
| 169 | // window.onscroll = function () { | 232 | // window.onscroll = function () { |
| ... | @@ -202,9 +265,10 @@ window.onscroll = function () { | ... | @@ -202,9 +265,10 @@ window.onscroll = function () { |
| 202 | } | 265 | } |
| 203 | } | 266 | } |
| 204 | 267 | ||
| 205 | -// 判断是否显示简介的展开图标 | 268 | +watch(() => bookInfo.value?.note, () => { |
| 206 | -const hasToggle = ref(false); // 判断是否有展开文字,默认没有 | 269 | + isToggle.value = true; |
| 207 | -const isToggle = ref(true); // 判断展开状态,默认展开 | 270 | + updateHasToggle(); |
| 271 | +}); | ||
| 208 | 272 | ||
| 209 | /** | 273 | /** |
| 210 | * 书籍订阅 | 274 | * 书籍订阅 |
| ... | @@ -327,6 +391,16 @@ onActivated(() => { // keepAlive 重置后执行回调 | ... | @@ -327,6 +391,16 @@ onActivated(() => { // keepAlive 重置后执行回调 |
| 327 | // 更新是否实名认证和儿童信息 | 391 | // 更新是否实名认证和儿童信息 |
| 328 | const data = useDefaultPerf($route.query.id); | 392 | const data = useDefaultPerf($route.query.id); |
| 329 | userInfo = data.userInfo; | 393 | userInfo = data.userInfo; |
| 394 | + bindIntroObserver(); | ||
| 395 | + updateHasToggle(); | ||
| 396 | +}); | ||
| 397 | + | ||
| 398 | +onDeactivated(() => { | ||
| 399 | + clearIntroObserver(); | ||
| 400 | +}); | ||
| 401 | + | ||
| 402 | +onBeforeUnmount(() => { | ||
| 403 | + clearIntroObserver(); | ||
| 330 | }); | 404 | }); |
| 331 | 405 | ||
| 332 | onBeforeRouteLeave(() => { | 406 | onBeforeRouteLeave(() => { | ... | ... |
-
Please register or login to post a comment