Taro(3.3.3)开发H5和百度小程序梳理(React)

507 阅读5分钟

Taro开发H5和百度小程序梳理(React)

  1. 近期做了一个h5和百度小程序开发需求,使用的框架是Taro,期间也遇到一些问题,所以对开发过程中遇见的一些问题和一些实现方案做了一些梳理

一:实现类似Tabs点击后,列表item选中项自动居中

截屏2022-08-18 下午6.45.50.png

截屏2022-08-18 下午6.47.08.png

背景:由于列表中内容比较多,为了提高用户体验效果,不需要用户手动滑动列表,所以采用用户点击后列表自动居中从而提高用户体验效果

(1)前期准备

  1. ScrollView taro-docs.jd.com/taro/docs/c…
  2. Taro.createSelectorQuery taro-docs.jd.com/taro/docs/a…
  3. Taro.nextTick taro-docs.jd.com/taro/docs/a…

ScrollView 踩坑点

  1. 在没有图片标签时,给item设置宽度以后无效,导致组件无法左右滑动
  2. 处理方案就是在item的父级加一层View
 <ScrollView
     scrollX
     scrollWithAnimation
     scrollLeft={scrollToLeftDay}
 >
 // 这里加一层,没有这一层的话,给下面的item设置宽度是没有效果的
     <View>
          <View style={{display: "flex"}}>
             // 父级外层加了一层View以后,再给item加宽度
              <View className='item'>item</View>
          </View>
     </View>
 </ScrollView>

Taro.createSelectorQuery 踩坑点

  1. Taro.createSelectorQuery.select('.className').fields
  2. Taro.createSelectorQuery.selectAll('.className').fields
  3. 以上 filed方法 在百度小程序环境中说无法获取元素的信息的,h5环境没有问题
  4. 还有就是Taro路由跳转就好使用Taro.redirectTo,因为使用其他跳转方式为关闭页面的话,taro会将页面保留在DOM树中导致相同的类名越来越多所以会导致获取到元素的位置是错误的,或者就是每次获取元素是做数组截取,获取最后几个也就是最新页面的
  5. 处理方案就是Taro.createSelectorQuery.selectAll('.className').boundingClientRect().exec替代

最终实现

  1. 当然在项目里面不止止是下面获取位置,还有很多项目内特殊的处理方式,代码较多,现在只是列出获取方式,大家如果要使用到可以根据自己项目的需求做特殊化处理,在这个基础上做好衍生就好了
 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;
        // 最终位置,赋值给ScrollView
        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首页联动滚动条效果

qq_pic_merged_1660837881789.jpg

背景:由于展示的资源位数量较多,根据设计稿的要求

踩坑点

  1. 单位问题,因为需要动态计算的原因,所以需要使用行内样式,但是编译后的代码,行内样式是不会被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 => {
        // 2(rem) 是滚动条灰色背景,10(vw)也是
        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>

三:实现日期和时间选择器

截屏2022-08-19 上午12.14.49.png

背景:由于项目要求日期和时间不分开,但是Taro框架内Picker 组件是日期和时间分开的,所以自己单独实现了一个

前期准备

  1. 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);
            // daylength 天数从1开始
            // days[val[2]] 索引从0开始
            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;
        } //若参数错误 返回-1
        let ms = m - 1;
        if (ms == 1) {
            //2月份的闰平规律测算后确认返回28或29
            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层级过高

背景:由于项目中需要绘制圆盘文案线条等等,所以选择画布进行统一绘制,但是由于同一页面需要使用到弹窗,所以此时便出现了弹窗层级超不过画布的情况

处理方案

  1. 使用Taro提供的CoverView, CoverImage 组件,但是由于不能嵌套更多,不满足项目内的样式布局所以项目中不使用此方案,如果项目中布局简单,嵌套层数不多的话可以采用此方案
  2. 将画布转换为图片,确保画布 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环境显示不全

  1. 在css里面添加transform: rotateZ(360deg); 即可

七:Taro百度小程序跳转

Taro.navigateToMiniProgram({
     appKey: resData.data.appid,  // 官方的api是 appid在百度小程序是错误的需要改为appKey
     path: resData.data.path
});

八:百度APP内浏览器环境

在百度APP浏览器环境内,无法使用 Taro.navigateTo 进页面跳转