微信小程序视频播放canvas进度条
<!--logs.wxml-->
<scroll-view class="scrollarea" scroll-y type="list">
<view class="container">
<video id="myVideo" src="{{videoSrc}}" controls="{{false}}" bindtimeupdate="onTimeUpdate"></video>
<canvas canvas-id="progressCanvas" class="progress-canvas" bindtouchstart="handleTouchStart" bindtouchmove="handleTouchMove" bindtouchend="handleTouchEnd"
bindtap="handleTap"
></canvas>
</view>
</scroll-view>
page {
height: 100vh;
display: flex;
flex-direction: column;
}
.scrollarea {
flex: 1;
overflow-y: hidden;
}
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
}
.progress-canvas {
width: 100%;
height: 20px;
margin-top: 10px;
}
/* 可以根据需要调整 */
.loading-indicator {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(0, 0, 0, 0.7);
color: white;
padding: 10px;
border-radius: 5px;
}
.progress-canvas, .buffer-canvas {
position: relative;
height: 10px;
}
// 'https://www.audi.cn/content/dam/OneWeb/faw_vw/audi_brand_campaign_content/2025/q6e-tron/video5_batch.mp4
Page({
data: {
videoSrc:
'https://www.audi.cn/content/dam/OneWeb/faw_vw/audi_brand_campaign_content/2025/q6e-tron/video5_batch.mp4',
videoDuration: 0,
currentTime: 0,
canvasWidth: 0,
canvasHeight: 0,
isDragging: false,
},
videoContext: null,
onLoad() {
this.videoContext = wx.createVideoContext('myVideo');
const query = wx.createSelectorQuery();
query.select('.progress-canvas').boundingClientRect((rect) => {
if (rect) {
this.setData({
canvasWidth: rect.width,
canvasHeight: rect.height,
});
this.drawProgress(); // 初始绘制
}
}).exec();
},
onReady() {
this.videoContext.play();
},
onTimeUpdate(e) {
const { currentTime, duration } = e.detail;
if (!this.data.isDragging) {
this.setData(
{
currentTime,
videoDuration: duration || this.data.videoDuration,
},
() => this._debounceDraw()
);
}
},
drawProgress() {
const ctx = wx.createCanvasContext('progressCanvas');
const { canvasWidth, canvasHeight, currentTime, videoDuration } = this.data;
// Clear the canvas
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
// Draw background bar
ctx.setFillStyle('#ccc');
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
// Calculate progress width
const progressWidth = (currentTime / videoDuration) * canvasWidth;
// Draw progress bar
ctx.setFillStyle('#ffcc00');
ctx.fillRect(0, 0, progressWidth, canvasHeight);
// Draw thumb circle
ctx.beginPath();
ctx.arc(progressWidth, canvasHeight / 2, 6, 0, Math.PI * 2);
ctx.setFillStyle('#fff');
ctx.fill();
ctx.stroke();
ctx.draw();
},
_drawTimeout: null,
_debounceDraw() {
clearTimeout(this._drawTimeout);
this._drawTimeout = setTimeout(() => {
this.drawProgress();
}, 50); // 防止过于频繁地重绘
},
handleTouchStart(e) {
this.setData({ isDragging: true });
},
handleTouchMove(e) {
if (!this.data.isDragging || !this.data.canvasWidth || !this.data.videoDuration) return;
const touchX = e.touches[0].pageX;
const newX = Math.max(0, Math.min(touchX, this.data.canvasWidth));
const newCurrentTime = (newX / this.data.canvasWidth) * this.data.videoDuration;
this.setData({ currentTime: newCurrentTime }, () => {
this.drawProgress();
});
},
handleTouchEnd() {
if (!this.data.isDragging) return;
this.setData({ isDragging: false }, () => {
this.videoContext.seek(this.data.currentTime);
});
},
handleTap(e) {
const tapX = e.detail.x;
const newCurrentTime = (tapX / this.data.canvasWidth) * this.data.videoDuration;
this.setData({ currentTime: newCurrentTime }, () => {
this.videoContext.seek(this.data.currentTime);
this.drawProgress();
});
},
});
html tab
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.tab-container {
display: flex;
position: relative;
}
.tab {
padding: 10px 20px;
cursor: pointer;
}
.tab.active {
font-weight: bold;
}
.indicator {
position: absolute;
bottom: 0;
height: 3px;
background-color: #000000; /* 可自定义颜色 */
border-radius: 3px;
transition: transform 0.3s ease;
}
</style>
</head>
<body>
<div class="tab-container">
<div class="tab active">选项</div>
<div class="tab">选项卡 2</div>
<div class="tab">新选项哈哈卡</div>
<div class="tab ">选项</div>
<div class="tab">选项卡 2</div>
<div class="tab">新选项哈哈卡</div>
<div class="indicator" style="width: 15px; transform: translateX(0);"></div>
</div>
<script>
const tabs = document.querySelectorAll('.tab');
const indicator = document.querySelector('.indicator');
function updateIndicator() {
const activeTab = document.querySelector('.tab.active');
const rect = activeTab.getBoundingClientRect();
const containerRect = document.querySelector('.tab-container').getBoundingClientRect();
const translateX = rect.left - containerRect.left + (rect.width - 15) / 2;
indicator.style.transform = `translateX(${translateX}px)`;
}
tabs.forEach((tab) => {
tab.addEventListener('click', () => {
tabs.forEach((t) => t.classList.remove('active'));
tab.classList.add('active');
updateIndicator();
});
});
updateIndicator();
</script>
</body>
</html>