|
|
<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>
|