一、需求背景
1、需要實現小程序新功能引導
2、不使用第三方庫(第三方組件試了幾個,都是各種兼容性問題,放棄)
二、實現步驟
1、寫一個公共的guide組件,代碼如下
components/Guide/index.tsx文件
import React, { useEffect, useState } from "react";
import Taro from "@tarojs/taro";
import { View, Button } from "@tarojs/components";
import { AtCurtain } from "taro-ui";import "./index.less";interface Props {// 需要指引的整體元素,會對此塊增加一個整體的蒙層,直接用guild元素包裹即可children: React.ReactNode;// 指引的具體domguildList: {content: string; // 指引內容id: string; // 指引的id --eg: 'test'}[];// 是否默認對被引導的dom添加高亮,只對子元素的子元素動態添加類名【cur-guide】isAuto?: boolean;// 1、此字段只對isAuto為false時生效// 2、部分頁面,需傳入此字段微調整距離頂部的距離otherHeight?: number;// 此字段只對isAuto為false時生效// activeGuide值變化時觸發,用來在isAuto為false時,告知外部需要高亮哪個dom,請外部根據此判斷添加類明【cur-guide】onChange?: (activeGuideId) => void;
}interface TaroElementProps {className?: string;children?: React.ReactNode;props: {id: string;[key: string]: any;};
}
type TaroElement = React.ReactElement<TaroElementProps>;const Guide = (props: Props) => {const [isOpened, setIsOpened] = useState(true);const [activeGuide, setActiveGuide] = useState(0);const [tipPosition, setTipPosition] = useState({top: 0,left: 0,});useEffect(() => {if (!props.isAuto) {updatePosition();props.onChange?.(props.guildList[activeGuide]?.id);}}, [activeGuide]);const updatePosition = () => {Taro.nextTick(() => {if (!props.guildList[activeGuide]) return;const query = Taro.createSelectorQuery();query.select(`#${props.guildList[activeGuide].id}`).boundingClientRect().selectViewport().scrollOffset().exec((res) => {if (res && res[0] && res[1]) {// res[0] 是元素的位置信息// res[1] 是頁面滾動的位置信息const rect = res[0];const scrollTop = res[1].scrollTop;// 計算元素距離頂部的實際距離(包含滾動距離)const actualTop = rect.top + scrollTop;setTipPosition({top:actualTop +rect.height -(props.otherHeight || 0) +12,left: rect.left + rect.width / 2,});}});});};const onPre = () => {if (activeGuide <= 0) {setActiveGuide(0);setIsOpened(false);return;}setActiveGuide(activeGuide - 1);};const onNext = () => {if (activeGuide >= props.guildList.length - 1) {setActiveGuide(props.guildList.length - 1);setIsOpened(false);return;}setActiveGuide(activeGuide + 1);};const renderTip = () => {return (<ViewclassName="cur-guide-tip"style={{top: `${tipPosition.top}px`,left: `${tipPosition.left}px`,}}><Button onClick={onPre}>上一步</Button><Button onClick={onNext}>下一步</Button></View>);};// 遞歸處理子元素,找到對應index的元素添加提示內容const enhanceChildren = (children: React.ReactNode) => {return React.Children.map(children, (child) => {if (!React.isValidElement(child)) return child;// 如果當前元素是數組(比如map渲染的列表),需要特殊處理if (child.props.children) {// 處理子元素const enhancedChildren = React.Children.map(child.props.children,(subChild) => {if (!React.isValidElement(subChild)) return subChild;const subChildProps = (subChild as TaroElement).props as any;const isCurrentActive =subChildProps.id === props.guildList[activeGuide]?.id;// 如果是當前激活的索引,為其添加提示內容if (isCurrentActive && isOpened) {const subChildProps = (subChild as TaroElement).props;return React.cloneElement(subChild as TaroElement, {className: `${subChildProps.className || ""} ${isCurrentActive ? "cur-guide" : ""}`,children: [...(Array.isArray(subChildProps.children)? subChildProps.children: [subChildProps.children]),renderTip(),],});}return subChild;});return React.cloneElement(child as TaroElement, {...child.props,children: enhancedChildren,});}return child;});};const renderBody = () => {return (<><View>{props.children}</View>{isOpened && renderTip()}</>);};const renderBodyAuto = () => {return <View>{enhanceChildren(props.children)}</View>;};return (<View className="fc-guide">{props.isAuto ? renderBodyAuto() : renderBody()}{isOpened && (<AtCurtain isOpened={isOpened} onClose={() => {}}></AtCurtain>)}</View>);
};
export default Guide;
components/Guide/index.less文件
.fc-guide {position: relative;.at-curtain {z-index: 20;.at-curtain__btn-close {display: none;}}// 這個是相對頂部距離的定位(isAuto為false時).cur-guide-tip {padding: 24px;background-color: #fff;position: absolute;z-index: 22;transform: translate(-50%, 0);}// 相對當前高亮元素的定位(isAuto為true時).cur-guide {background: #f5f5f5;position: relative;z-index: 22;.cur-guide-tip {bottom: 0 !important;left: 50% !important;transform: translate(-50%, 100% + 12px);}}
}
2、使用方式
a.isAuto為true時的傳值結構
b.isAuto為false時
需要配合onChange事件將當前激活id傳給父組件,然后父組件再根據當前激活id去選擇高亮哪個dom元素(類名判斷寫在和id設置同一個dom上),然后給對應dom綁上’cur-guide‘類名即可