主要特点是支持手势滑动,支持判断站点是否在可视范围中,文字竖向布局效果
一、HTML
<view class="routevialist">
<scroll-view class="schedulelist schedulelist-average{{ViewModel.viaList.length}}" scroll-x scroll-with-animation scroll-left="{{ViewModel.scrollLeft}}">
<block wx:for="{{ViewModel.viaList}}" wx:key="index">
<view class="viapoint-right {{viatype+'-viapoint-color'}} {{index == 0?'startpoint':''}} {{index ==ViewModel.viaList.length-1?'endpoint':''}}" wx:key="index" bindtap="onClickSelectVia" data-index="{{index}}" id="{{'viapoint-'+index}}">
<!-- 起点 -->
<image src="/busubway/resources/image/vialistimg/start-via.png" class="start-via" wx:if="{{index ==0}}" />
<!-- 终点 -->
<image src="/busubway/resources/image/vialistimg/end-via.png" class="end-via" wx:elif="{{index==ViewModel.viaList.length-1}}" />
<!-- 途经点 -->
<block wx:else>
<image src="/busubway/resources/image/vialistimg/checked-arrow.png" class="checked-arrow" wx:if="{{index == ViewModel.viaCheckedIndex}}" />
<image src="/busubway/resources/image/vialistimg/arrow-right.png" class="arrow-right" wx:else />
</block>
<!-- 白色箭头 -->
<image src="/busubway/resources/image/vialistimg/arrow-down.png" class="arrow-down" wx:if="{{index == ViewModel.viaCheckedIndex}}" />
<!-- 公交车 -->
<image src="/busubway/resources/image/vialistimg/bus.png" class="bus" wx:if="{{item.bus}}" />
<!-- 站点名称 -->
<view class="viapoint {{index == ViewModel.viaCheckedIndex?'viapoint-checked':''}}">{{item.vianame}}</view>
<!-- 附近地铁 -->
<block wx:if="{{item.subway&&item.subway.length}}">
<view class="subway-tag" wx:for="{{item.subway}}" wx:key="index">{{item}}</view>
</block>
<!-- 离我最近 -->
<view class="distance-tag {{index ==ViewModel.viaList.length-1?'distance-tag-last':''}}" wx:if="{{item.nearest}}">离我最近</view>
</view>
</block>
</scroll-view>
<view class="auto-scroll-view auto-scroll-left" hidden="{{!ViewModel.scrollLeftShow}}" bindtap="onClickMoveTo">
<image src="/busubway/resources/image/vialistimg/{{viatype}}-left.png" class="auto-img" />
<view class="auto-left-text {{viatype+'-color'}}">
{{ViewModel.viaList[ViewModel.viaCheckedIndex].vianame}}
</view>
</view>
<view class="auto-scroll-view auto-scroll-right" hidden="{{!ViewModel.scrollRightShow}}" bindtap="onClickMoveTo">
<image src="/busubway/resources/image/vialistimg/{{viatype}}-right.png" class="auto-img" />
<view class="auto-left-text {{viatype+'-color'}}">
{{ViewModel.viaList[ViewModel.viaCheckedIndex].vianame}}
</view>
</view>
</view>
二、CSS
.routevialist {
position: relative;
padding: 0 30rpx;
}
.schedulelist {
white-space: nowrap;
box-sizing: border-box;
}
.viapoint-right {
padding-top: 150rpx;
width: 120rpx;
display: inline-block;
position: relative;
}
.schedulelist-average6 .viapoint-right {
width: 130rpx;
}
.schedulelist-average5 .viapoint-right {
width: 160rpx;
}
.schedulelist-average4 .viapoint-right {
width: 215rpx;
}
.schedulelist-average3 .viapoint-right {
width: 325rpx;
}
.schedulelist-average2 .viapoint-right {
width: 645rpx;
}
.bus-viapoint-color::before {
content: "";
position: absolute;
width: 100%;
height: 10rpx;
background-color: #26925e;
top: 110rpx;
z-index: 1;
}
.subway-viapoint-color::before {
content: "";
position: absolute;
width: 100%;
height: 10rpx;
background-color: #3e85ee;
top: 110rpx;
z-index: 1;
}
.schedulelist .startpoint::before {
left: 20rpx;
}
.schedulelist .endpoint {
width: 40rpx;
}
.schedulelist .endpoint::before {
width: 50%;
}
.start-via,
.end-via {
width: 42rpx;
height: 42rpx;
position: absolute;
top: 96rpx;
z-index: 10;
}
.arrow-right {
width: 8rpx;
height: 10rpx;
position: absolute;
z-index: 9;
top: 110rpx;
left: 18rpx;
}
.checked-arrow {
width: 48rpx;
height: 48rpx;
position: absolute;
top: 92rpx;
z-index: 11;
left: -4rpx;
}
.bus {
width: 54rpx;
height: 32rpx;
position: absolute;
top: 60rpx;
z-index: 11;
left: -4rpx;
}
.arrow-down {
width: 32rpx;
height: 30rpx;
position: absolute;
top: 0rpx;
z-index: 11;
left: 4rpx;
}
.viapoint {
vertical-align: top;
font-size: 28rpx;
letter-spacing: 4rpx;
writing-mode: vertical-lr;
writing-mode: tb-lr;
color: #666666;
display: inline-block;
white-space: normal;
text-align: center;
margin-bottom: 10rpx;
/* 设置数字或者字母字符水平展示 */
text-orientation: upright;
}
.viapoint-checked {
font-size: 30rpx;
font-weight: bold;
color: #333333;
}
.distance-tag {
vertical-align: top;
background-color: #e9f2ff;
border-radius: 4px;
font-size: 18rpx;
letter-spacing: 4rpx;
writing-mode: vertical-lr;
writing-mode: tb-lr;
color: #3e85ee;
width: 30rpx;
display: flex;
justify-content: center;
align-items: center;
white-space: normal;
text-align: center;
margin-bottom: 10rpx;
/* 设置数字或者字母字符水平展示 */
text-orientation: upright;
padding: 8rpx 0;
position: absolute;
top: 150rpx;
left: 44rpx;
}
.distance-tag-last {
position: relative;
left: 4rpx;
top: 0;
}
.subway-tag {
vertical-align: top;
font-size: 18rpx;
letter-spacing: 4rpx;
writing-mode: vertical-lr;
writing-mode: tb-lr;
color: #fff;
white-space: normal;
background-color: #3e85ee;
border-radius: 13px;
width: 30rpx;
display: flex;
justify-content: center;
align-items: center;
padding: 8rpx 0;
text-align: center;
margin-bottom: 10rpx;
position: relative;
left: 4rpx;
/* 设置数字或者字母字符水平展示 */
text-orientation: upright;
}
/* 设置数字不被拆开换行 */
/* .subway-tag text {
text-combine-upright: all;
} */
.auto-scroll-view{
position: absolute;
top: 150rpx;
width: 47rpx;
background-color: #ffffff;
box-shadow: 0rpx 0rpx 12rpx 0rpx
rgba(0, 0, 0, 0.14);
border-radius: 10rpx;
z-index: 20;
display: flex;
align-items: center;
flex-direction: column;
justify-content: center;
padding: 12rpx 0;
}
.auto-scroll-left{
left: 30rpx;
}
.auto-scroll-right{
right: 30rpx;
}
.auto-img{
width: 17rpx;
height: 15rpx;
margin-bottom: 10rpx;
}
.auto-left-text{
vertical-align: top;
font-size: 28rpx;
letter-spacing: 4rpx;
writing-mode: vertical-lr;
writing-mode: tb-lr;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
position: relative;
/* 设置数字或者字母字符水平展示 */
text-orientation: upright;
}
.bus-color{
color: #26925e;
}
.subway-color{
color: #3e85ee;
}
三、JS
// busubway/components/via-list/via-list.js
var app = getApp()
import {
commonApi
} from "../../../utils/api.js";
var util = require('../../../utils/util.js');
//150为每个站点宽度(120)+外层容器padding(30)
const setingLeft = -app.globalData.screenWidth / 750 * 120;
const setingRight = -app.globalData.screenWidth / 750 * 30;
Component({
/**
* 组件的属性列表
*/
properties: {
/**
* 站点类型:bus或者subway
*/
viatype: {
type: String,
// value: "subway",
value: "bus",
}
},
lifetimes: {
ready() {
this.createObserver()
},
detached() {
this.observerDisconnect()
},
},
/**
* 组件的初始数据
*/
data: {
ViewModel: {
HOST_URL: commonApi.HOST_URL,
viaCheckedIndex: 8,
viaList: [{
vianame: "城子",
subway: [],
nearest: "",
bus: false
},
{
vianame: "城子北站",
subway: ["7号线"],
nearest: "离我最近",
bus: true
},
{
vianame: "首钢小区",
subway: ['2号线', "7号线"],
nearest: "",
bus: false
},
{
vianame: "金顶南路",
subway: [],
nearest: "",
bus: false
},
{
vianame: "地铁苹果园站",
subway: [],
nearest: "",
bus: true
},
{
vianame: "地铁苹果园站14",
subway: [],
nearest: "",
bus: false
},
{
vianame: "地铁苹果园站15",
subway: [],
nearest: "",
bus: false
},
{
vianame: "地铁苹果园站16",
subway: [],
nearest: "",
bus: false
},
{
vianame: "地铁苹果园站17",
subway: [],
nearest: "",
bus: false
},
{
vianame: "地铁苹果园站18",
subway: [],
nearest: "",
bus: false
},
{
vianame: "地铁苹果园站19",
subway: [],
nearest: "",
bus: false
},
{
vianame: "地铁苹果园站20",
subway: [],
nearest: "",
bus: false
},
{
vianame: "地铁西二旗站",
subway: ['13号线', "昌平线"],
nearest: "",
bus: false
}, {
vianame: "上地五街",
subway: [],
nearest: "",
bus: true
}, {
vianame: "唐家岭东",
subway: [],
nearest: "",
bus: false
}, {
vianame: "史各庄公交场站",
subway: ["7号线"],
nearest: "离我最近",
bus: false
}
],
scrollLeft: 0,
scrollLeftShow: false,
scrollRightShow: false,
intersectionObserver: null
}
},
/**
* 组件的方法列表
*/
methods: {
observerDisconnect() {
var that = this
that.data.ViewModel.intersectionObserver.disconnect()
that.setData({
"ViewModel.intersectionObserver": null
})
},
createObserver() {
var that = this
var viaCheckedIndex = that.data.ViewModel.viaCheckedIndex
var intersectionObserver = that.data.ViewModel.intersectionObserver
if (intersectionObserver) {
that.observerDisconnect()
}
var callback = (res) => {
// console.log(res)
//目标边界
var boundingClientRect = res.boundingClientRect
//相交比例
var intersectionRatio = res.intersectionRatio
//参照区域的边界
var relativeRect = res.relativeRect
if (intersectionRatio == 0) {
if (boundingClientRect.left + boundingClientRect.width <= relativeRect.left) {
that.setData({
"ViewModel.scrollLeftShow": true,
"ViewModel.scrollRightShow": false,
})
} else if (boundingClientRect.left + boundingClientRect.width >= relativeRect.right) {
that.setData({
"ViewModel.scrollLeftShow": false,
"ViewModel.scrollRightShow": true,
})
}
} else {
that.setData({
"ViewModel.scrollLeftShow": false,
"ViewModel.scrollRightShow": false,
})
}
}
that.moveTo(viaCheckedIndex)
var observer = that.createIntersectionObserver()
observer.relativeToViewport({
left: setingLeft,
right: setingRight,
bottom: 0
}).observe('#viapoint-' + viaCheckedIndex, callback)
that.setData({
"ViewModel.intersectionObserver": observer
})
},
onClickMoveTo() {
this.moveTo(this.data.ViewModel.viaCheckedIndex)
},
onClickSelectVia(e) {
var that = this;
var index = e.currentTarget.dataset.index
if (index == that.data.ViewModel.viaCheckedIndex) {
return
}
that.setData({
"ViewModel.viaCheckedIndex": index
})
that.createObserver()
that.triggerEvent("onViaChange", that.data.ViewModel.viaList[index])
},
moveTo: function (index) {
var that = this;
const query = wx.createSelectorQuery().in(this);
query.selectAll('.viapoint-right').boundingClientRect();
query.exec((res) => {
var rect = res[0];
let width = 0;
// 循环获取计算当前点击的标签项距离左侧的距离
for (let i = 0; i < index; i++) {
width += rect[i].width
}
// 当大于屏幕一半的宽度则滚动,否则就设置位置为0
let clientWidth = wx.getSystemInfoSync().windowWidth / 2;
if (width > clientWidth) {
that.setData({
"ViewModel.scrollLeft": width + rect[index].width / 2 - clientWidth
})
} else {
that.setData({
"ViewModel.scrollLeft": 0
})
}
})
},
}
})
四、参考资料
完爆scroll事件,交叉观察器 IntersectionObserver 在千万级PV页面中的应用实践