You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

223 lines
7.7 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div
ref="boxRef"
class="box"
@scroll.passive="handleScroll"
>
<div v-loading="loading">
<div
v-for="(item, index) in list"
:key="index"
class="html-item"
>
<h2>标题:{{ item.title }}</h2>
<div
v-html="item.html"
@click="handleClickHtml($event, item)"
></div>
<el-divider />
</div>
</div>
</div>
</template>
<script setup lang="jsx">
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue';
import { debounce } from '@pureadmin/utils';
const boxRef = ref(null);
const list = ref([]);
const loading = ref(false);
const videoAddEventListeners = ref([]);
/**
* 将富文本内的所有点击链接,点击图片,点击视频,点击按钮等绑定埋点事件
*/
const html = `<a href='https://www.baidu.com' style='text-decoration: underline;'>点击链接</a><br /><br/>
<img src='https://www.baidu.com/img/flexible/logo/pc/result.png' /><br /><br/>
<button style='background-color: #4CAF50; border: none; color: white; padding: 10px 20px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; margin: 4px 2px; cursor: pointer;'>按钮</button>
<video src="https://www.w3school.com.cn/i/movie.ogg" controls="controls"></video>
<video controls="controls"><source src="https://www.w3school.com.cn/i/movie.mp4" type="video/ogg"></video>
<a href='https://www.baidu.com' style='text-decoration: underline;'><span>有内容标签的链接</span></a><br /><br/>
<a href='https://www.baidu.com' style='text-decoration: underline;'><img alt='可以跳转的图片' src='https://www.baidu.com/img/flexible/logo/pc/result.png' /></a><br /><br/>
`;
const getHerfAttribute = (dom) => {
return dom.getAttribute('href') || dom.href || '';
};
// 通过v-html渲染的富文本模拟事件委托
const handleClickHtml = async (event, item) => {
const currentDom = event.target;
const parentDom = currentDom.parentNode;
let currentDomHref = getHerfAttribute(currentDom);
let parentHref = getHerfAttribute(parentDom);
// 跳转链接
let jumpLink = '';
// 埋点参数
let params = {};
switch (currentDom.tagName) {
case 'A':
event.preventDefault();
jumpLink = currentDomHref;
params.link = currentDomHref;
params.type = 'link';
break;
case 'IMG':
if (parentDom.tagName === 'A') {
event.preventDefault();
jumpLink = parentHref;
params.link = parentHref;
}
params.type = 'image';
params.src = currentDom.getAttribute('src') || currentDom.src;
break;
case 'BUTTON':
params.type = 'button';
params.text = currentDom.textContent;
break;
default:
if (parentDom.tagName === 'A') {
event.preventDefault();
jumpLink = parentHref;
params.link = parentHref;
params.type = 'link';
}
break;
}
// 有需要埋点的事件
if (params.type) {
await new Promise((resolve) => {
params.title = item.title;
setTimeout(() => {
console.log(`触发${params.type}埋点事件`);
console.table(params);
resolve();
}, 500);
});
// 跳转链接
if (jumpLink) {
console.log(`跳转链接${jumpLink}`);
// window.location.href = jumpLink;
}
}
};
const videoBuryingPoint = (type, src, title) => {
console.log(`视频${type}事件视频地址${src}, 视频所属标题${title}`);
};
const videoAddEventListener = () => {
let videos = boxRef.value.querySelectorAll('video');
for (let i = 0; i < videos.length; i++) {
const video = videos[i];
// 防止重复绑定事件
if (!video.dataset.listenerAdded) {
//video 属性上有src则直接通过video.src获取视频地址
const src = video.src;
//video 内有source标签则只能通过获取source标签的src来获取视频地址
const sources = video.getElementsByTagName('source');
//如果有多个source标签则取第一个source标签的src
const sourceSrc = sources.length ? sources[0].src : '';
// 查找最近的祖先节点,直到找到具有 'html-item' 类名的元素
const ancestorWithHtmlItemClass = video.closest('.html-item');
const title = ancestorWithHtmlItemClass.querySelector('h2').textContent;
const playHandler = () => videoBuryingPoint('play', src || sourceSrc, title);
const pauseHandler = () => videoBuryingPoint('pause', src || sourceSrc, title);
const endedHandler = () => videoBuryingPoint('ended', src || sourceSrc, title);
const volumeChangeHandler = () => videoBuryingPoint('volumechange', src || sourceSrc, title);
video.addEventListener('play', playHandler);
video.addEventListener('pause', pauseHandler);
video.addEventListener('ended', endedHandler);
video.addEventListener('volumechange', volumeChangeHandler);
// 存储事件处理函数以便后续移除
videoAddEventListeners.value.push({
video,
playHandler,
pauseHandler,
endedHandler,
volumeChangeHandler,
});
video.dataset.listenerAdded = true;
}
}
};
const videoRemoveEventListener = () => {
videoAddEventListeners.value.forEach((item) => {
const { video, playHandler, pauseHandler, endedHandler, volumeChangeHandler } = item;
video.removeEventListener('play', playHandler);
video.removeEventListener('pause', pauseHandler);
video.removeEventListener('ended', endedHandler);
video.removeEventListener('volumechange', volumeChangeHandler);
});
videoAddEventListeners.value = [];
};
const getData = () => {
loading.value = true;
console.log('加载数据');
setTimeout(() => {
loading.value = false;
let tempList = [];
let currentLength = list.value.length;
for (let i = 0; i < 5; i++) {
tempList.push({
title: `模拟数据${i + currentLength}`,
html,
});
}
list.value = list.value.concat(tempList);
nextTick(() => {
videoAddEventListener();
});
}, 2000);
};
const scrollLoadData = () => {
console.log('监听滚动事件');
/**
* scrollTop滚动元素的顶部到可视区域顶部的距离。
* clientHeight可视区域的高度。
* scrollHeight内容的总高度。
*/
const { scrollTop, clientHeight, scrollHeight } = boxRef.value;
console.log(scrollTop, clientHeight, scrollHeight);
if (scrollTop + clientHeight >= scrollHeight - 20) {
console.log('距离底部还有20px, 加载数据');
getData();
}
};
const handleScroll = debounce(scrollLoadData, 500);
onMounted(() => {
getData();
});
onBeforeUnmount(() => {
videoRemoveEventListener();
});
</script>
<style lang="scss" scoped>
.box {
display: flex;
width: 100%;
flex-direction: column;
align-items: center;
background: #fff;
// 要监听滚动事件必须设置高度并且设置滚动为auto否则监听事件不生效
height: calc(100vh - 81px);
overflow-y: auto;
& > div {
width: 100%;
padding: 10px 20px;
}
}
</style>