feat: 新增 ui-skeleton 骨架屏组件 (card / list-row / circle)
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
Component({
|
||||
properties: {
|
||||
variant: { type: String, value: 'card' },
|
||||
count: { type: Number, value: 1 },
|
||||
active: { type: Boolean, value: true }
|
||||
},
|
||||
data: {
|
||||
_minDisplayShown: false
|
||||
},
|
||||
lifetimes: {
|
||||
attached() {
|
||||
// 200ms 最小显示,避免骨架闪一下
|
||||
this._minTimer = setTimeout(() => {
|
||||
this.setData({ _minDisplayShown: true })
|
||||
}, 200)
|
||||
},
|
||||
detached() {
|
||||
if (this._minTimer) clearTimeout(this._minTimer)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<view class="skeleton {{active ? 'skeleton--active' : ''}}" wx:if="{{_minDisplayShown || !active}}">
|
||||
<!-- card variant -->
|
||||
<block wx:if="{{variant === 'card'}}">
|
||||
<view class="sk-card">
|
||||
<view class="sk-bar sk-bar--title"></view>
|
||||
<view class="sk-bar sk-bar--body"></view>
|
||||
<view class="sk-bar sk-bar--small"></view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- list-row variant -->
|
||||
<block wx:elif="{{variant === 'list-row'}}">
|
||||
<view class="sk-list" wx:for="{{count}}" wx:key="*this">
|
||||
<view class="sk-circle"></view>
|
||||
<view class="sk-list-text">
|
||||
<view class="sk-bar sk-bar--title"></view>
|
||||
<view class="sk-bar sk-bar--small"></view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- circle variant (timer) -->
|
||||
<block wx:elif="{{variant === 'circle'}}">
|
||||
<view class="sk-circle sk-circle--big">
|
||||
<view class="sk-bar sk-bar--center"></view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
@@ -0,0 +1,85 @@
|
||||
.skeleton {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.skeleton--active .sk-bar,
|
||||
.skeleton--active .sk-circle {
|
||||
animation: skShimmer 1.4s ease-in-out infinite;
|
||||
background: linear-gradient(90deg,
|
||||
rgba(0, 0, 0, 0.06) 0%,
|
||||
rgba(0, 0, 0, 0.12) 50%,
|
||||
rgba(0, 0, 0, 0.06) 100%);
|
||||
background-size: 200% 100%;
|
||||
}
|
||||
|
||||
@keyframes skShimmer {
|
||||
0% { background-position: 200% 0; }
|
||||
100% { background-position: -200% 0; }
|
||||
}
|
||||
|
||||
.sk-bar {
|
||||
height: 24rpx;
|
||||
border-radius: 8rpx;
|
||||
margin-bottom: 16rpx;
|
||||
background-color: rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.sk-bar--title { width: 60%; height: 32rpx; }
|
||||
.sk-bar--body { width: 100%; }
|
||||
.sk-bar--small { width: 40%; height: 20rpx; margin-bottom: 0; }
|
||||
.sk-bar--center { width: 60%; height: 40rpx; }
|
||||
|
||||
.sk-card {
|
||||
background: var(--card-bg, #FFFFFF);
|
||||
border-radius: 24rpx;
|
||||
padding: 32rpx;
|
||||
box-shadow: 0 2rpx 16rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.sk-circle {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(0, 0, 0, 0.08);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sk-circle--big {
|
||||
width: 360rpx;
|
||||
height: 360rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.sk-list {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24rpx 0;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.sk-list-text {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
/* dark mode */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.sk-card { background: var(--card-bg, #1C1C1E); }
|
||||
.skeleton--active .sk-bar,
|
||||
.skeleton--active .sk-circle {
|
||||
background: linear-gradient(90deg,
|
||||
rgba(255, 255, 255, 0.05) 0%,
|
||||
rgba(255, 255, 255, 0.10) 50%,
|
||||
rgba(255, 255, 255, 0.05) 100%);
|
||||
background-size: 200% 100%;
|
||||
}
|
||||
.sk-bar,
|
||||
.sk-circle {
|
||||
background-color: rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user