Taro开发H5和百度小程序梳理(React)
- 近期做了一个h5和百度小程序开发需求,使用的框架是Taro,期间也遇到一些问题,所以对开发过程中遇见的一些问题和一些实现方案做了一些梳理
一:实现类似Tabs点击后,列表item选中项自动居中


背景:由于列表中内容比较多,为了提高用户体验效果,不需要用户手动滑动列表,所以采用用户点击后列表自动居中从而提高用户体验效果
(1)前期准备
- ScrollView taro-docs.jd.com/taro/docs/c…
- Taro.createSelectorQuery taro-docs.jd.com/taro/docs/a…
- Taro.nextTick taro-docs.jd.com/taro/docs/a…
ScrollView 踩坑点
- 在没有图片标签时,给item设置宽度以后无效,导致组件无法左右滑动
- 处理方案就是在item的父级加一层View
<ScrollView
scrollX
scrollWithAnimation
scrollLeft={scrollToLeftDay}
>
<View>
<View style={{display: "flex"}}>
// 父级外层加了一层View以后,再给item加宽度
<View className='item'>item</View>
</View>
</View>
</ScrollView>
Taro.createSelectorQuery 踩坑点
- Taro.createSelectorQuery.select('.className').fields
- Taro.createSelectorQuery.selectAll('.className').fields
- 以上 filed方法 在百度小程序环境中说无法获取元素的信息的,h5环境没有问题
- 还有就是Taro路由跳转就好使用Taro.redirectTo,因为使用其他跳转方式为关闭页面的话,taro会将页面保留在DOM树中导致相同的类名越来越多所以会导致获取到元素的位置是错误的,或者就是每次获取元素是做数组截取,获取最后几个也就是最新页面的
- 处理方案就是Taro.createSelectorQuery.selectAll('.className').boundingClientRect().exec替代
最终实现
- 当然在项目里面不止止是下面获取位置,还有很多项目内特殊的处理方式,代码较多,现在只是列出获取方式,大家如果要使用到可以根据自己项目的需求做特殊化处理,在这个基础上做好衍生就好了
let day: dateData[] = [];
const [scrollAreaLeftDay, setScrollAreaLeftDay] = useState(0);
const [scrollAreaWidthDay, setScrollAreaWidthDay] = useState(0);
const [tabItemListDay, setTabItemListDay] = useState([]);
const [scrollToLeftDay, setScrollToLeftDay] = useState(0);
useEffect(()=>{
Taro.nextTick(()=>{
initScrollData(
".date-picker-day",
".date-picker-item-day"
);
})
},[])
const initScrollData = (dom1, dom2) => {
const query = Taro.createSelectorQuery();
const query2 = Taro.createSelectorQuery();
query
.select(dom1)
.boundingClientRect()
.exec(res => {
setScrollAreaLeftDay(res[0].left);
setScrollAreaWidthDay(res[0].width);
});
query2
.selectAll(dom2)
.boundingClientRect()
.exec(res => {
arrDay = [];
res[0].forEach(item => {
arrDay.push({
width: item.width,
left: item.left
});
});
setTabItemListDay(arrDay);
});
};
const changeDay = (index) => {
if (index === dayCurrent && !firstTime) return;
const offsetLeft = tabItemListDay[index].left - scrollAreaLeftDay;
const scrollLeft =
offsetLeft - (scrollAreaWidthDay - tabItemListDay[index].width) / 2;
setScrollToLeftDay(scrollLeft < 0 ? 0 : scrollLeft);
};
<ScrollView
scrollX
scrollWithAnimation
scrollLeft={scrollToLeftDay}
>
<View className="date-picker-day>
<View style={{display: "flex"}}>
{
day.map(()=>{
return (
<View className='date-picker-item-day' onClick={() => {
changeDay();
}}>
<View>item</View>
</View>
)}
</View>
</View>
</ScrollView>
二:实现淘宝App首页联动滚动条效果

背景:由于展示的资源位数量较多,根据设计稿的要求
踩坑点
- 单位问题,因为需要动态计算的原因,所以需要使用行内样式,但是编译后的代码,行内样式是不会被taro自动转换单位的,所以在不同手机会出现不是响应的问题,所以需要在h5环境使用rem单位,百度使用vw单位
最终实现
const { advertisingData } = props;
const [dataLength, setDataLength] = useState(4); 每页一行展示的数量
const [scrollbgc, setScrollbgc] = useState("");
const [scrollDistance, setScrollDistance] = useState("");
const [domWidth, setDomWidth] = useState("");
useEffect(() => {
const DomWidth: string =
process.env.TARO_ENV === "h5"
? 3.583 * dataLength + "rem"
: 22.5 * dataLength + "vw";
setDomWidth(DomWidth);
}, [dataLength]);
useEffect(() => {
let showLength = advertisingData.items.length;
const length = Math.ceil(showLength / 2);
setDataLength(length);
if (process.env.TARO_ENV === "h5") {
setScrollbgc((2 * 4.15 * 3.583) / (length * 3.583) + "rem");
} else {
setScrollbgc((10 * 4.15 * 22.5) / (length * 22.5) + "vw");
}
}, [advertisingData]);
const theScrollData = e => {
if (process.env.TARO_ENV === "h5") {
setScrollDistance(
(2 * e.detail.scrollLeft) / e.detail.scrollWidth + "rem"
);
} else {
setScrollDistance(
(10 * e.detail.scrollLeft) / e.detail.scrollWidth + "vw"
);
}
};
<View className="advertising-box">
<ScrollView scrollX onScroll={theScrollData}>
<View
className="scroll-box"
style={{
width: domWidth
}}
>
{showData.map((v, k) => {
return (
<View
key={k}
className="scroll-box-item"
style={{
padding:
process.env.TARO_ENV === "h5"
? "16px 0.596rem 0"
: "16px 3.780vw 0"
}}
onClick={() => {
gotoPage(v);
}}
>
item
</View>
);
})}
</View>
</ScrollView>
<View
className="scroll-details"
style={{
width: process.env.TARO_ENV === "h5" ? "2rem" : "10vw"
}}
>
<View
className="scroll-details-item"
style={{
width: scrollbgc,
left: scrollDistance
}}
></View>
</View>
</View>
三:实现日期和时间选择器

背景:由于项目要求日期和时间不分开,但是Taro框架内Picker 组件是日期和时间分开的,所以自己单独实现了一个
前期准备
- PickerView,PickerViewColumn 组件
最终实现
const date = new Date();
const inityears = [];
const initmonths = [];
const initdays = [];
const initTime = [];
const initMinutes = [];
for (let i = 1910; i <= date.getFullYear(); i++) {
inityears.push(i);
}
for (let i = 1; i <= 12; i++) {
let index: any = 0;
if (i < 10) {
index = "0" + i;
} else {
index = i;
}
initmonths.push(index);
}
for (let i = 1; i <= 31; i++) {
let index: any = 0;
if (i < 10) {
index = "0" + i;
} else {
index = i;
}
initdays.push(index);
}
for (let i = 0; i <= 23; i++) {
let index: any = 0;
if (i < 10) {
index = "0" + i;
} else {
index = i;
}
initTime.push(index);
}
for (let i = 0; i <= 59; i++) {
let index: any = 0;
if (i < 10) {
index = "0" + i;
} else {
index = i;
}
initMinutes.push(index);
}
const { data } = props;
const [years] = useState(inityears);
const [year, setYear] = useState("");
const [months] = useState(initmonths);
const [month, setMonth] = useState("");
const [days, setDays] = useState(initdays);
const [day, setDay] = useState("");
const [times] = useState(initTime);
const [time, setTime] = useState("");
const [minutess] = useState(initMinutes);
const [minutes, setMinutes] = useState("");
const [value, setValue] = useState([0, 0, 0, 0, 0]);
const solarMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
const [show, setShow] = useState(false);
useEffect(() => {
const birthday = String(data.birthday),
y = birthday.slice(0, 4),
m = birthday.slice(4, 6),
d = birthday.slice(6, 8),
t = birthday.slice(8, 10),
mi = birthday.slice(10, 12);
setYear(y);
setMonth(m);
setDay(d);
setTime(t);
setMinutes(mi);
}, [data]);
useEffect(() => {
if (show) {
setValue([0, 0, 0, 0, 0]);
let yearIndex = 0;
let monthIndex = 0;
let dayIndex = 0;
let timeIndex = 0;
let minutesIndex = 0;
years.find((value, index) => {
if (Number(value) === Number(year)) {
return (yearIndex = index);
}
});
months.find((value, index) => {
if (Number(value) === Number(month)) {
return (monthIndex = index);
}
});
days.find((value, index) => {
if (Number(value) === Number(day)) {
return (dayIndex = index);
}
});
times.find((value, index) => {
if (Number(value) === Number(time)) {
return (timeIndex = index);
}
});
minutess.find((value, index) => {
if (Number(value) === Number(minutes)) {
return (minutesIndex = index);
}
});
setTimeout(() => {
setValue([
yearIndex,
monthIndex,
dayIndex,
timeIndex,
minutesIndex
]);
}, 500);
}
}, [show]);
const onChange = e => {
const val = e.detail.value;
if (months[val[1]] !== month) {
const daylength = solarDays(years[val[0]], months[val[1]]);
const newDay: number[] = [];
for (let i = 1; i <= daylength; i++) {
let index: any = 0;
if (i < 10) {
index = "0" + i;
} else {
index = i;
}
newDay.push(index);
}
setDays(newDay);
if (daylength < days[val[2]] + 1) {
setYear(String(years[val[0]]));
setMonth(months[val[1]]);
setDay(daylength - 1);
setTime(times[val[3]]);
setMinutes(minutess[val[4]]);
setValue([val[0], val[1], daylength - 1, val[3], val[4]]);
return;
}
}
setYear(String(years[val[0]]));
setMonth(months[val[1]]);
setDay(days[val[2]]);
setTime(times[val[3]]);
setMinutes(minutess[val[4]]);
setValue(val);
};
const solarDays = (y, m) => {
if (m > 12 || m < 1) {
return -1;
}
let ms = m - 1;
if (ms == 1) {
return (y % 4 == 0 && y % 100 != 0) || y % 400 == 0 ? 29 : 28;
} else {
return solarMonth[ms];
}
};
const sumbit = () => {
props.getTime(year, month, day, time, minutes);
setShow(false);
};
<PickerView
indicatorStyle="height: 40px"
indicatorClass="pan-data-picker-active"
style="width: 100%; height: 300px;flex:20% 20% 20% 20% 20%"
value={value}
onChange={onChange}
>
<PickerViewColumn style="flex: 0 0 0 0 20%">
{years.map(item => {
return (
<View
style={{
height: "40px",
lineHeight: "40px",
textAlign: "center",
overflow: "hidden",
backgroundColor: "#f8f9f7"
}}
>
{item}年
</View>
);
})}
</PickerViewColumn>
<PickerViewColumn style="flex: 0 0 0 0 20%">
{months.map(item => {
return (
<View
style={{
height: "40px",
lineHeight: "40px",
textAlign: "center",
overflow: "hidden",
backgroundColor: "#f8f9f7"
}}
>
{item}月
</View>
);
})}
</PickerViewColumn>
<PickerViewColumn style="flex: 0 0 0 0 20%">
{days.map(item => {
return (
<View
style={{
height: "40px",
lineHeight: "40px",
textAlign: "center",
overflow: "hidden",
backgroundColor: "#f8f9f7"
}}
>
{item}日
</View>
);
})}
</PickerViewColumn>
<PickerViewColumn style="flex: 0 0 0 0 20%">
{times.map(item => {
return (
<View
style={{
height: "40px",
lineHeight: "40px",
textAlign: "center",
overflow: "hidden",
backgroundColor: "#f8f9f7"
}}
>
{item}时
</View>
);
})}
</PickerViewColumn>
<PickerViewColumn style="flex: 0 0 0 0 20%">
{minutess.map(item => {
return (
<View
style={{
height: "40px",
lineHeight: "40px",
textAlign: "center",
overflow: "hidden",
backgroundColor: "#f8f9f7"
}}
>
{item}分
</View>
);
})}
</PickerViewColumn>
</PickerView>
四:Canvas层级过高
背景:由于项目中需要绘制圆盘文案线条等等,所以选择画布进行统一绘制,但是由于同一页面需要使用到弹窗,所以此时便出现了弹窗层级超不过画布的情况
处理方案
- 使用Taro提供的CoverView, CoverImage 组件,但是由于不能嵌套更多,不满足项目内的样式布局所以项目中不使用此方案,如果项目中布局简单,嵌套层数不多的话可以采用此方案
- 将画布转换为图片,确保画布 draw 完成以后调用 Taro.canvasToTempFilePath
Taro.canvasToTempFilePath({
x: 0,
y: 0,
width: phoneWidthNum,
height: phoneWidthNum,
canvasId: "stage-pan-bg",
success: res1 => {
setCanvasbgc(res1.tempFilePath);
}
});
五:Taro使用Canvas
创建
ctx = Taro.createCanvasContext("stage-pan-bg")
画布移动和放大缩小
ctx.translate(phoneWidthNum / 2, phoneWidthNum / 2);
ctx.scale(
0.8 + (phoneWidthNum - 320) * 0.003,
0.8 + (phoneWidthNum - 320) * 0.003
);
画圆
ctx.beginPath();
ctx.arc(0, 0, R0, 0, 2 * Math.PI);
ctx.setFillStyle("#ffffff");
ctx.fill();
ctx.setStrokeStyle("#f0e9ff");
ctx.setLineDash([2, 2]);
ctx.stroke();
ctx.beginPath();
ctx.arc(0, 0, R1, 0, 2 * Math.PI);
ctx.setFillStyle("#eae1ff");
ctx.fill();
ctx.setStrokeStyle("#9a70ff");
ctx.setLineDash([0, 0]);
ctx.stroke();
ctx.beginPath();
ctx.arc(0, 0, R3, 0, 2 * Math.PI);
ctx.setFillStyle("#f4f2ff");
ctx.fill();
ctx.setLineWidth(0.5);
ctx.setStrokeStyle("#9a70ff");
ctx.stroke();
画线
ctx.beginPath();
ctx.moveTo(...start);
ctx.setLineWidth(1);
ctx.setStrokeStyle(color);
ctx.lineTo(...end);
ctx.stroke();
文案绘制
ctx.font = `${22 / multiple}px Microsoft YaHei`;
ctx.setFillStyle(color);
ctx.setTextAlign("center");
ctx.setTextBaseline("middle");
ctx.fillText(value, x, y);
六:css边框在ios环境显示不全
- 在css里面添加transform: rotateZ(360deg); 即可
七:Taro百度小程序跳转
Taro.navigateToMiniProgram({
appKey: resData.data.appid,
path: resData.data.path
});
八:百度APP内浏览器环境
在百度APP浏览器环境内,无法使用 Taro.navigateTo 进页面跳转