uni-app的专属强大自适应单位upx,但是这个这是一个大坑,不能动态赋值解决办法

uni-app 使用 upx 作为默认尺寸单位, upx 是相对于基准宽度的单位,可以根据屏幕宽度进行自适应。uni-app 规定屏幕基准宽度750upx。

开发者可以通过设计稿基准宽度计算页面元素 upx 值,设计稿 1px 与框架样式 1upx 转换公式如下:

设计稿 1px / 设计稿基准宽度 = 框架样式 1upx / 750upx

 

举例说明:

 

  1. 若设计稿宽度为 640px,元素 A 在设计稿上的宽度为 100px,那么元素 A 在 uni-app 里面的宽度应该设为750 * 100 / 640,结果为:117upx
  2. 若设计稿宽度为 375px,元素 B 在设计稿上的宽度为 200px,那么元素 B 在 uni-app 里面的宽度应该设为750 * 200 / 375结果为:400upx

 

1、动态绑定的 style 不支持直接使用 upx

<!-- - 静态upx赋值生效 -->
<view class="test" style="width:200upx"></view>
<!-- - 动态绑定不生效 -->
<view class="test" :style="{width:winWidth + 'upx;'}"></view>

2、使用 uni.upx2px(Number) 转换为 px 后再赋值。

<template>
    <view>
        <view class="half-width" :style="{width: halfWidth}">
            半屏宽度
        </view>
    </view>
</template>

<script>
    export default {
        computed: {
            halfWidth() {
                return uni.upx2px(750 / 2) + 'px';
            }
        }
    }
</script>
<style>
    .half-width {
        background-color: #FF3333;
    }
</style>
<think>在uni-app中,由于跨平台兼容性考虑,避免使用`requestAnimationFrame`,我们可以使用`setTimeout`或`setInterval`来模拟动画效果。但是更推荐使用uni-app自带的动画API或者CSS动画。不过,对于Canvas绘图,我们通常使用定时器来实现动画。由于我们需要在Canvas上绘制环形进度条,并且希望有动画效果(从0增长到目标进度),我们可以使用`setTimeout`递归或者`setInterval`来实现逐步绘制。考虑到性能,我们可以使用`setTimeout`递归,因为这样我们可以控制每一步的时间间隔,并且在动画结束时清除定时器。以下是一个使用`setTimeout`实现动画的环形进度条组件:组件思路:1.使用Canvas绘制一个背景圆环(灰色)。2.根据当前进度值,绘制一个彩色圆环(覆盖在背景圆环上)。3.使用动画逐步增加当前绘制的进度,直到达到目标进度。注意:在uni-app中,我们需要使用`uni.createCanvasContext`来创建绘图上下文,并且使用`rpx`单位,但Canvas绘制时使用像素(px),因此需要将rpx转换为px。我们将创建一个组件,它接收`progress`(目标进度)、`color`(进度条颜色)、`bgColor`(背景颜色)、`size`(尺寸,单位rpx)、`strokeWidth`(圆环宽度,单位rpx)等属性。具体实现步骤:-在模板中放置一个canvas元素,设置其宽高(通过style设置,单位rpx,同时需要设置canvas-id)。-在mounted时初始化Canvas,绘制背景圆环,并开始动画。-使用一个定时器,每隔一段时间(如16ms,接近60帧)更新当前进度,并重绘进度圆环。-当当前进度达到目标进度时,清除定时器。另外,我们还需要在中心位置显示进度文本。下面给出完整代码:</think># Uni-App 中使用定时器实现的 Canvas 环形进度条 我将为您提供一个使用 `setTimeout` 替代 `requestAnimationFrame` 的 Canvas 环形进度条实现方案,完全兼容 Uni-App 环境。 ## 完整组件代码(使用 setTimeout) ```vue <template> <view class="canvas-container"> <canvas :id="canvasId" :canvas-id="canvasId" class="progress-canvas" :style="{ width: size + 'rpx', height: size + 'rpx' }" ></canvas> <view class="progress-text" :style="textStyle"> {{ displayValue }}<text v-if="showPercentSymbol">%</text> </view> </view> </template> <script> export default { props: { progress: { type: Number, default: 0, validator: value => value >= 0 && value <= 100 }, color: { type: [String, Array], default: '#2979ff' }, bgColor: { type: String, default: '#ebedf0' }, size: { type: Number, default: 200 }, strokeWidth: { type: Number, default: 12 }, showPercentSymbol: { type: Boolean, default: true }, animationDuration: { type: Number, default: 800 }, formatter: { type: Function, default: null } }, data() { return { canvasId: `canvas_${Date.now()}`, displayValue: 0, ctx: null, currentProgress: 0, animationTimer: null }; }, computed: { textStyle() { return { fontSize: `${this.size * 0.2}rpx`, color: this.textColor || '#333', fontWeight: this.fontWeight || 'bold' }; }, pxSize() { return uni.upx2px(this.size); }, pxStrokeWidth() { return uni.upx2px(this.strokeWidth); }, radius() { return (this.pxSize - this.pxStrokeWidth) / 2; }, // 计算动画帧间隔时间 frameInterval() { // 根据动画时长自动计算帧间隔(目标60fps) return Math.max(10, Math.floor(this.animationDuration / 60)); } }, watch: { progress(newVal) { this.animateProgressChange(newVal); } }, mounted() { this.initCanvas(); this.animateProgressChange(this.progress); }, beforeDestroy() { this.clearAnimation(); }, methods: { // 初始化 Canvas initCanvas() { this.ctx = uni.createCanvasContext(this.canvasId, this); this.drawBackground(); }, // 绘制背景圆环 drawBackground() { this.drawArc(0, 360, this.bgColor, this.pxStrokeWidth); }, // 绘制圆弧 drawArc(startAngle, endAngle, color, lineWidth) { const center = this.pxSize / 2; this.ctx.setLineWidth(lineWidth); this.ctx.setLineCap('round'); // 处理渐变色 if (Array.isArray(color)) { const gradient = this.ctx.createLinearGradient(0, 0, this.pxSize, this.pxSize); color.forEach((c, i) => { gradient.addColorStop(i / (color.length - 1), c); }); this.ctx.setStrokeStyle(gradient); } else { this.ctx.setStrokeStyle(color); } // 绘制圆弧 this.ctx.beginPath(); this.ctx.arc( center, center, this.radius, (startAngle - 90) * Math.PI / 180, (endAngle - 90) * Math.PI / 180 ); this.ctx.stroke(); // 绘制到画布 this.ctx.draw(); }, // 使用定时器实现动画 animateProgressChange(targetProgress) { this.clearAnimation(); const startTime = Date.now(); const startProgress = this.currentProgress; const animateStep = () => { const elapsed = Date.now() - startTime; let progress = Math.min(elapsed / this.animationDuration, 1); // 应用缓动函数 progress = this.easeOutCubic(progress); this.currentProgress = startProgress + (targetProgress - startProgress) * progress; this.displayValue = this.formatter ? this.formatter(Math.round(this.currentProgress)) : Math.round(this.currentProgress); this.redrawCanvas(); if (progress < 1) { this.animationTimer = setTimeout(animateStep, this.frameInterval); } else { this.currentProgress = targetProgress; this.displayValue = this.formatter ? this.formatter(Math.round(this.currentProgress)) : Math.round(this.currentProgress); } }; this.animationTimer = setTimeout(animateStep, this.frameInterval); }, // 缓动函数 easeOutCubic(t) { return 1 - Math.pow(1 - t, 3); }, // 重绘Canvas redrawCanvas() { // 清除Canvas上下文状态 this.ctx.clearRect(0, 0, this.pxSize, this.pxSize); // 重新绘制背景 this.drawBackground(); // 计算进度圆弧的角度 const progressAngle = (this.currentProgress / 100) * 360; // 绘制进度圆弧 if (progressAngle > 0) { this.drawArc(0, progressAngle, this.color, this.pxStrokeWidth); } }, // 清除动画 clearAnimation() { if (this.animationTimer) { clearTimeout(this.animationTimer); this.animationTimer = null; } } } }; </script> <style lang="scss" scoped> .canvas-container { position: relative; display: inline-flex; justify-content: center; align-items: center; .progress-canvas { // 确保Canvas元素可见 } .progress-text { position: absolute; text-align: center; pointer-events: none; z-index: 10; } } </style> ``` ## 组件使用示例 ```vue <template> <view class="container"> <canvas-progress :progress="progressValue" :color="['#FF9A9E', '#FAD0C4']" bg-color="#f5f5f5" :size="220" :strokeWidth="16" :animationDuration="1000" /> <view class="controls"> <button class="btn" @click="setProgress(0)">0%</button> <button class="btn" @click="setProgress(25)">25%</button> <button class="btn" @click="setProgress(50)">50%</button> <button class="btn" @click="setProgress(75)">75%</button> <button class="btn" @click="setProgress(100)">100%</button> </view> <slider :value="progressValue" min="0" max="100" @change="onSliderChange" class="slider" /> </view> </template> <script> import CanvasProgress from '@/components/canvas-progress.vue' export default { components: { CanvasProgress }, data() { return { progressValue: 30 } }, methods: { setProgress(value) { this.progressValue = value; }, onSliderChange(e) { this.progressValue = e.detail.value; } } } </script> <style scoped> .container { display: flex; flex-direction: column; justify-content: center; align-items: center; height: 100vh; padding: 30rpx; background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); } .controls { display: flex; flex-wrap: wrap; justify-content: center; margin: 40rpx 0; .btn { margin: 10rpx; padding: 15rpx 25rpx; border-radius: 50rpx; background-color: #5b86e5; color: white; font-size: 28rpx; box-shadow: 0 4rpx 12rpx rgba(91, 134, 229, 0.3); } } .slider { width: 80%; margin-top: 30rpx; } </style> ``` ## 实现原理详解 ### 1. 定时器动画系统 - 使用 `setTimeout` 替代 `requestAnimationFrame` - 自动计算帧间隔时间 (`frameInterval`) - 通过 `clearTimeout` 管理动画生命周期 - 在组件销毁时自动清理定时器 ### 2. 动画控制逻辑 ```javascript animateProgressChange(targetProgress) { this.clearAnimation(); const startTime = Date.now(); const startProgress = this.currentProgress; const animateStep = () => { // 计算时间进度 const elapsed = Date.now() - startTime; let progress = Math.min(elapsed / this.animationDuration, 1); // 应用缓动函数 progress = this.easeOutCubic(progress); // 更新当前进度值 this.currentProgress = startProgress + (targetProgress - startProgress) * progress; // 重绘Canvas this.redrawCanvas(); // 递归调用直到完成 if (progress < 1) { this.animationTimer = setTimeout(animateStep, this.frameInterval); } }; // 启动动画 this.animationTimer = setTimeout(animateStep, this.frameInterval); } ``` ### 3. 性能优化措施 1. **自动帧率调整**:根据动画时长计算最佳帧间隔 ```javascript frameInterval() { return Math.max(10, Math.floor(this.animationDuration / 60)); } ``` 2. **动画清理机制**: ```javascript clearAnimation() { if (this.animationTimer) { clearTimeout(this.animationTimer); this.animationTimer = null; } } ``` 3. **高效重绘**:只绘制必要的元素 ```javascript redrawCanvas() { this.ctx.clearRect(0, 0, this.pxSize, this.pxSize); this.drawBackground(); const progressAngle = (this.currentProgress / 100) * 360; if (progressAngle > 0) { this.drawArc(0, progressAngle, this.color, this.pxStrokeWidth); } } ``` ### 4. 跨平台兼容性 - 使用 `uni.createCanvasContext` 创建绘图上下文 - 通过 `uni.upx2px` 处理单位转换 - 避免使用平台特定API - 兼容 H5、小程序和 App 平台 ## 关键优势 1. **完全兼容 Uni-App**:不使用 `requestAnimationFrame`,避免兼容性问题 2. **平滑动画效果**:使用缓动函数实现流畅过渡 3. **自动性能优化**:根据动画时长调整帧率 4. **内存管理**:组件销毁时自动清理资源 5. **支持渐变效果**:线性渐变进度条 6. **响应式设计**:自适应不同屏幕尺寸 ## 使用场景 - 数据仪表盘 - 文件上传/下载进度 - 任务完成度展示 - 游戏进度指示器 - 健康/健身应用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值