/**
|
|
* 可见性监听混入
|
|
* 提供元素可见性监听功能,支持H5、微信小程序、App端
|
|
* 用于实现浏览记录等功能
|
|
*/
|
|
import { VIEWPORT_CONFIG, createBrowseRecordParams } from '@/config/browseConfig.js'
|
|
|
|
export default {
|
|
data() {
|
|
return {
|
|
hasRecordedView: false, // 标记是否已记录浏览
|
|
viewTimer: null, // 浏览计时器
|
|
isInViewport: false, // 是否在可视区域内
|
|
observer: null, // 观察者实例
|
|
}
|
|
},
|
|
|
|
mounted() {
|
|
// 组件挂载后,使用Intersection Observer监听可见性
|
|
this.observeVisibility()
|
|
},
|
|
|
|
beforeDestroy() {
|
|
// 组件销毁前清理observer和计时器
|
|
this.cleanupObserver()
|
|
},
|
|
|
|
methods: {
|
|
/**
|
|
* 监听元素可见性
|
|
* @param {String} selector - 要监听的元素选择器,默认为'.works'
|
|
* @param {Object} config - 配置参数
|
|
*/
|
|
observeVisibility(selector = '.works', config = {}) {
|
|
const {
|
|
threshold = VIEWPORT_CONFIG.THRESHOLD,
|
|
dwellTime = VIEWPORT_CONFIG.DWELL_TIME,
|
|
margins = VIEWPORT_CONFIG.VIEWPORT_MARGINS
|
|
} = config
|
|
|
|
// #ifdef H5
|
|
if (typeof IntersectionObserver !== 'undefined') {
|
|
this.observer = new IntersectionObserver((entries) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting && !this.hasRecordedView) {
|
|
// 元素进入可视区域且未记录过浏览
|
|
this.handleViewportEnter(dwellTime)
|
|
} else if (!entry.isIntersecting && this.isInViewport) {
|
|
// 元素离开可视区域
|
|
this.handleViewportLeave()
|
|
}
|
|
})
|
|
}, {
|
|
threshold: threshold
|
|
})
|
|
|
|
this.observer.observe(this.$el)
|
|
} else {
|
|
// 降级方案:延迟记录浏览
|
|
setTimeout(() => {
|
|
if (!this.hasRecordedView) {
|
|
this.recordBrowseView()
|
|
}
|
|
}, dwellTime)
|
|
}
|
|
// #endif
|
|
|
|
// #ifdef MP-WEIXIN
|
|
// 使用微信小程序原生的IntersectionObserver API
|
|
this.observer = wx.createIntersectionObserver(this, {
|
|
thresholds: [threshold],
|
|
initialRatio: 0,
|
|
observeAll: false
|
|
})
|
|
|
|
// 指定页面显示区域作为参照区域
|
|
this.observer.relativeToViewport(margins)
|
|
|
|
// 开始监听目标节点
|
|
this.observer.observe(selector, (res) => {
|
|
if (res.intersectionRatio > threshold && !this.hasRecordedView) {
|
|
// 当元素达到配置阈值以上进入可视区域且未记录过浏览时,处理进入可视区域事件
|
|
this.handleViewportEnter(dwellTime)
|
|
} else if (res.intersectionRatio <= threshold && this.isInViewport) {
|
|
// 元素离开可视区域
|
|
this.handleViewportLeave()
|
|
}
|
|
})
|
|
// #endif
|
|
|
|
// #ifdef APP-PLUS
|
|
// App端使用uni-app的createIntersectionObserver
|
|
this.observer = uni.createIntersectionObserver(this, {
|
|
thresholds: [threshold],
|
|
initialRatio: 0,
|
|
observeAll: false
|
|
})
|
|
|
|
this.observer.relativeToViewport(margins)
|
|
|
|
this.observer.observe(selector, (res) => {
|
|
if (res.intersectionRatio > threshold && !this.hasRecordedView) {
|
|
this.handleViewportEnter(dwellTime)
|
|
} else if (res.intersectionRatio <= threshold && this.isInViewport) {
|
|
this.handleViewportLeave()
|
|
}
|
|
})
|
|
// #endif
|
|
},
|
|
|
|
/**
|
|
* 处理进入可视区域事件
|
|
* @param {Number} dwellTime - 停留时间
|
|
*/
|
|
handleViewportEnter(dwellTime = VIEWPORT_CONFIG.DWELL_TIME) {
|
|
if (this.hasRecordedView) return
|
|
|
|
this.isInViewport = true
|
|
|
|
// 设置延迟计时器,确保用户真正浏览了内容
|
|
this.viewTimer = setTimeout(() => {
|
|
if (this.isInViewport && !this.hasRecordedView) {
|
|
this.recordBrowseView()
|
|
}
|
|
}, dwellTime)
|
|
},
|
|
|
|
/**
|
|
* 处理离开可视区域事件
|
|
*/
|
|
handleViewportLeave() {
|
|
this.isInViewport = false
|
|
|
|
// 清除计时器,如果用户快速滚动过去,不记录浏览
|
|
if (this.viewTimer) {
|
|
clearTimeout(this.viewTimer)
|
|
this.viewTimer = null
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 记录浏览行为
|
|
* @param {String} itemType - 项目类型,默认为'dynamic'
|
|
* @param {String} itemId - 项目ID,默认从this.item.id获取
|
|
*/
|
|
recordBrowseView(itemType = 'dynamic', itemId = null) {
|
|
const id = itemId || (this.item && this.item.id)
|
|
|
|
if (this.hasRecordedView || !id) return
|
|
|
|
this.hasRecordedView = true
|
|
|
|
// 清除计时器
|
|
if (this.viewTimer) {
|
|
clearTimeout(this.viewTimer)
|
|
this.viewTimer = null
|
|
}
|
|
|
|
// 使用配置文件中的参数创建API请求参数
|
|
const params = createBrowseRecordParams(id, itemType)
|
|
|
|
// 调用浏览记录API
|
|
this.$api('addBrowseRecord', params, res => {
|
|
if (res.code === 200) {
|
|
console.log('浏览记录已保存:', id)
|
|
}
|
|
})
|
|
},
|
|
|
|
/**
|
|
* 清理观察者和计时器
|
|
*/
|
|
cleanupObserver() {
|
|
if (this.observer) {
|
|
// #ifdef H5
|
|
if (typeof this.observer.disconnect === 'function') {
|
|
this.observer.disconnect()
|
|
}
|
|
// #endif
|
|
|
|
// #ifdef MP-WEIXIN || APP-PLUS
|
|
if (typeof this.observer.disconnect === 'function') {
|
|
this.observer.disconnect()
|
|
}
|
|
// #endif
|
|
|
|
this.observer = null
|
|
}
|
|
|
|
if (this.viewTimer) {
|
|
clearTimeout(this.viewTimer)
|
|
this.viewTimer = null
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 重置浏览记录状态
|
|
*/
|
|
resetBrowseRecord() {
|
|
this.hasRecordedView = false
|
|
this.isInViewport = false
|
|
this.cleanupObserver()
|
|
}
|
|
}
|
|
}
|