|
|
@@ -4,7 +4,10 @@
|
|
|
@scroll="onScroll" @touchend="onTouchEnd" @touchcancel="onTouchEnd" scroll-into-view="">
|
|
|
<view class="ruler-inner" :style="{ width: totalWidth + 'px' }">
|
|
|
<view v-for="(g, idx) in gridList" :key="idx" class="grid-item" :class="{ long: g.isLongGrid }"
|
|
|
- :style="{ width: props.gutter + 'px', height: g.showText ? '40px' : '24px' }">
|
|
|
+ :style="{ width: props.gutter + 'px' }">
|
|
|
+ <!-- 刻度线(位于上方) -->
|
|
|
+ <view class="tick" :class="{ long: g.isLongGrid }"></view>
|
|
|
+ <!-- 刻度文字放在刻度线下面 -->
|
|
|
<text v-if="g.showText" class="grid-num">{{ g.displayNum }}</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
@@ -73,17 +76,17 @@ const halfWindow = windowWidth / 2
|
|
|
|
|
|
// initial position
|
|
|
onMounted(() => {
|
|
|
- // center on initial value
|
|
|
+ // center on initial value (align cell center under indicator)
|
|
|
const initIndex = Math.round((props.initialValue - props.min) / props.step)
|
|
|
- // place the right-edge of the initIndex cell under the center indicator
|
|
|
- scrollLeft.value = offsetScroll.value + (initIndex + 1) * props.gutter - halfWindow
|
|
|
+ // place the center of the initIndex cell under the center indicator
|
|
|
+ scrollLeft.value = offsetScroll.value + initIndex * props.gutter + props.gutter / 2 - halfWindow
|
|
|
})
|
|
|
|
|
|
function onScroll(e: any) {
|
|
|
const left = e?.detail?.scrollLeft ?? e?.detail?.scrollLeft ?? 0
|
|
|
actualScrollLeft.value = left
|
|
|
- // compute number index (units from min) under the centered indicator
|
|
|
- const numIndex = Math.round((left + halfWindow - offsetScroll.value) / props.gutter) - 1
|
|
|
+ // compute number index (units from min) under the centered indicator (based on cell center)
|
|
|
+ const numIndex = Math.round((left + halfWindow - offsetScroll.value - props.gutter / 2) / props.gutter)
|
|
|
let value = numIndex * props.step + props.min
|
|
|
if (value < props.min) value = props.min
|
|
|
if (value > props.max) value = props.max
|
|
|
@@ -94,9 +97,9 @@ function adjustScrollPosition() {
|
|
|
// snap so the nearest tick is centered under the indicator
|
|
|
const left = actualScrollLeft.value
|
|
|
// compute number index under center
|
|
|
- const numIndex = Math.round((left + halfWindow - offsetScroll.value) / props.gutter) - 1
|
|
|
- // compute target scrollLeft that centers this index's right-edge under the indicator
|
|
|
- const targetLeft = offsetScroll.value + (numIndex + 1) * props.gutter - halfWindow
|
|
|
+ const numIndex = Math.round((left + halfWindow - offsetScroll.value - props.gutter / 2) / props.gutter)
|
|
|
+ // compute target scrollLeft that centers this index's center under the indicator
|
|
|
+ const targetLeft = offsetScroll.value + numIndex * props.gutter + props.gutter / 2 - halfWindow
|
|
|
// clamp to valid scroll range
|
|
|
const maxScroll = Math.max(0, totalWidth.value - windowWidth)
|
|
|
const clamped = Math.min(Math.max(targetLeft, 0), maxScroll)
|
|
|
@@ -108,8 +111,8 @@ function onTouchEnd() {
|
|
|
adjustScrollPosition()
|
|
|
// compute value and emit change
|
|
|
const left = scrollLeft.value
|
|
|
- const numIndex = Math.round((left + halfWindow - offsetScroll.value) / props.gutter) - 1
|
|
|
- let value = numIndex * props.step + props.min
|
|
|
+ const numIndex = Math.round((left + halfWindow - offsetScroll.value - props.gutter / 2) / props.gutter)
|
|
|
+ let value = numIndex * props.step + props.min
|
|
|
if (value < props.min) value = props.min
|
|
|
if (value > props.max) value = props.max
|
|
|
emit('change', value)
|
|
|
@@ -119,38 +122,49 @@ function onTouchEnd() {
|
|
|
<style scoped>
|
|
|
.ruler-wrapper {
|
|
|
position: relative;
|
|
|
- height: 80rpx;
|
|
|
+ /* 增高以给刻度线下面留白 */
|
|
|
+ height: 120rpx;
|
|
|
display: flex;
|
|
|
- align-items: center
|
|
|
+ align-items: flex-start;
|
|
|
}
|
|
|
|
|
|
.ruler-scroll {
|
|
|
width: 100%;
|
|
|
- height: 80rpx
|
|
|
+ height: 120rpx;
|
|
|
}
|
|
|
|
|
|
.ruler-inner {
|
|
|
display: flex;
|
|
|
- align-items: flex-end
|
|
|
+ /* 让刻度线靠上排列,文字在下方 */
|
|
|
+ align-items: flex-start;
|
|
|
}
|
|
|
|
|
|
.grid-item {
|
|
|
width: 10px;
|
|
|
- height: 24px;
|
|
|
+ /* 不固定高度,让内部内容控制布局 */
|
|
|
display: flex;
|
|
|
- align-items: flex-end;
|
|
|
- justify-content: center;
|
|
|
- border-right: 1px solid #ccc
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: flex-start;
|
|
|
+ padding-top: 8rpx; /* 给刻度线一点顶部间距 */
|
|
|
}
|
|
|
|
|
|
-.grid-item.long {
|
|
|
- height: 40px;
|
|
|
- border-right: 2px solid #333
|
|
|
+
|
|
|
+/* 刻度线样式,短刻度和长刻度高度不同 */
|
|
|
+.tick {
|
|
|
+ width: 2px;
|
|
|
+ height: 24rpx;
|
|
|
+ background: #ccc;
|
|
|
+}
|
|
|
+.tick.long {
|
|
|
+ height: 40rpx;
|
|
|
+ background: #333;
|
|
|
}
|
|
|
|
|
|
.grid-num {
|
|
|
font-size: 24rpx;
|
|
|
- color: #666
|
|
|
+ color: #666;
|
|
|
+ margin-top: 8rpx; /* 刻度线下面的间距 */
|
|
|
}
|
|
|
|
|
|
.indicator {
|
|
|
@@ -160,6 +174,6 @@ function onTouchEnd() {
|
|
|
transform: translateX(-50%);
|
|
|
width: 2px;
|
|
|
height: 100%;
|
|
|
- background: #ff6a00
|
|
|
+ background: #ff6a00;
|
|
|
}
|
|
|
</style>
|