| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130 |
- <template>
- <div class="title" data-chapterpos="0" ref="titleRef">{{ title }}</div>
- <div
- v-for="(para, index) in contents"
- :key="index"
- ref="paragraphRef"
- :data-chapterpos="chapterPos[index]"
- >
- <img
- class="full"
- v-if="/^\s*<img[^>]*src[^>]+>$/.test(para)"
- :src="getImageSrc(para)"
- @error.once="proxyImage"
- loading="lazy"
- />
- <p v-else :style="{ fontFamily, fontSize }" v-html="para" />
- </div>
- </template>
- <script setup>
- import { getImageFromLegado, isLegadoUrl } from "@/plugins/utils";
- import jump from "@/plugins/jump";
- const props = defineProps({
- chapterIndex: { type: Number, required: true },
- contents: { type: Array, required: true },
- title: { type: String, required: true },
- spacing: { type: Object, required: true },
- fontFamily: { type: String, required: true },
- fontSize: { type: String, required: true },
- });
- const getImageSrc = (content) => {
- const imgPattern = /<img[^>]*src="([^"]*(?:"[^>]+\})?)"[^>]*>/;
- const src = content.match(imgPattern)[1];
- if (isLegadoUrl(src)) return getImageFromLegado(src);
- return src;
- };
- const proxyImage = (event) => {
- event.target.src = getImageFromLegado(event.target.src);
- };
- const calculateWordCount = (paragraph) => {
- const imgPattern = /<img[^>]*src="[^"]*(?:"[^>]+\})?"[^>]*>/g;
- //内嵌图片文字为1
- const imagePlaceHolder = " ";
- return paragraph.replaceAll(imgPattern, imagePlaceHolder).length;
- };
- const chapterPos = computed(() => {
- let pos = -1;
- return Array.from(props.contents, (content) => {
- pos += calculateWordCount(content) + 1; //计算上一段的换行符
- return pos;
- });
- });
- const titleRef = ref();
- const paragraphRef = ref();
- const scrollToReadedLength = (length) => {
- if (length === 0) return;
- let paragraphIndex = chapterPos.value.findIndex(
- (wordCount) => wordCount >= length
- );
- if (paragraphIndex === -1) return;
- nextTick(() => {
- jump(paragraphRef.value[paragraphIndex], {
- duration: 0,
- });
- });
- };
- defineExpose({
- scrollToReadedLength,
- });
- const observer = ref(null);
- const emit = defineEmits(["readedLengthChange"]);
- onMounted(() => {
- observer.value = new IntersectionObserver(
- (entries) => {
- for (let { target, isIntersecting } of entries) {
- if (isIntersecting) {
- emit(
- "readedLengthChange",
- props.chapterIndex,
- parseInt(target.dataset.chapterpos)
- );
- }
- }
- },
- {
- rootMargin: `0px 0px -${window.innerHeight - 24}px 0px`,
- }
- );
- observer.value.observe(titleRef.value);
- paragraphRef.value.forEach((element) => {
- observer.value.observe(element);
- });
- });
- onUnmounted(() => {
- observer.value?.disconnect();
- observer.value = null;
- });
- </script>
- <style lang="scss" scoped>
- .title {
- margin-bottom: 57px;
- font: 24px / 32px PingFangSC-Regular, HelveticaNeue-Light,
- "Helvetica Neue Light", "Microsoft YaHei", sans-serif;
- }
- p {
- display: block;
- word-wrap: break-word;
- word-break: break-all;
- letter-spacing: calc(v-bind("props.spacing.letter") * 1em);
- line-height: calc(1 + v-bind("props.spacing.line"));
- margin: calc(v-bind("props.spacing.paragraph") * 1em) 0;
- :deep(img) {
- height: 1em;
- }
- }
- .full {
- display: block;
- width: 100%;
- }
- </style>
|