当前位置: 首页 > article >正文

原生微信小程序实现导航漫游(Tour)

效果:

小程序实现导航漫游

1、组件

miniprogram/components/tour/index.wxml

<!--wxml-->
<view class="guide" wx:if="{{showGuide}}">
  <view style="{{guideStyle}}" class="guide-box">
    <view class="tips guide-step-tips" style="{{tipPosition}}">
      <view class="text">{{ guideList[index].tips }}</view>
      <view class="step-indicator">
        <text class="step-text">{{index + 1}}/{{guideList.length}}</text>
      </view>
      <view class="tool-btn">
        <text bind:tap="skip">跳过</text>
        <view class="btn-group">
          <view class="prev" bind:tap="prev" wx:if="{{index > 0}}">上一步</view>
          <view class="next" bind:tap="next">{{index === guideList.length - 1 ? '完成' : '下一步'}}</view>
        </view>
      </view>
    </view>
    <view class="arrow" style="{{arrowTop}}"></view>
  </view>
  <!-- 遮罩层,防止点击 -->
  <view class="v-model"></view>
</view>

miniprogram/components/tour/index.ts 

// components/xky-guideStep/xky-guideStep.js

import { handleChangeTourType } from "@/utils/util";

// 添加必要的接口定义
interface GuideItem {
  el: string;
  width: number;
  height: number;
  left: number;
  top: number;
  style?: string;
}

interface Step {
  name: string;
  guideList: GuideItem[];
}

interface DomInfo {
  width: number;
  height: number;
  left: number;
  top: number;
  [key: string]: any;
}

Component({
  /**
   * 组件的属性列表
   */
  properties: {
    step: {
      type: Object,
      value: {} as Step,
    },
  },

  /**
   * 组件的初始数据
   */
  data: {
    stepName: "step", //该提示步骤的名称,用于不在重复展示
    guideList: [] as GuideItem[],
    index: 0, // 当前展示的索引
    showGuide: true, // 是否显示引导
    guideStyle: "", // 默认样式
    arrowTop: "", //步骤提示三角形的定位
    tipPosition: "", //步骤提示的定位
    systemInfo: null as WechatMiniprogram.SystemInfo | null, //屏幕宽度高度等信息
    tipWidth: 200, //步骤提示默认的宽度
  },

  /**
   * 组件的方法列表
   */
  methods: {
    // 展示新手提示
    viewTips(data: DomInfo | null, scrollTop: number) {
      let {
        systemInfo,
        tipWidth,
        index,
        guideList,
        arrowTop,
        tipPosition,
        guideStyle,
      } = this.data;
      if (data && systemInfo) {
        // 如果dom宽度大于或者等于窗口宽度,需要重新调整dom展示宽度
        let newWidth = systemInfo.windowWidth - 20;
        if (data.width >= newWidth) {
          data.width = newWidth;
        }
        // 如果距离左边为0,自动增加一点左边距
        if (data.left == 0) {
          data.left = 10;
        }
        let domRW = systemInfo.windowWidth - data.left;
        let left = 0;
        // 如果dom距离右边没有tips的宽度大的话,就要让tips向左便宜
        if (domRW < tipWidth) {
          left = domRW - tipWidth - 30;
        }
        // const index = index;
        // 步骤条展示的高度需要加上屏幕滚动的高度
        data.top += scrollTop;
        // 根据实际情况需要滚动到展示区域
        wx.pageScrollTo({
          scrollTop: data.top > 20 ? data.top - 20 : 0,
          duration: 100,
        });
        let obj = Object.assign(guideList[index], data);
        // 设置三角形高度
        let arrArrowTop = data.height + 9;
        arrowTop = "top:" + arrArrowTop + "px;";
        // 设置提示框定位
        tipPosition = "top:" + (arrArrowTop + 5) + "px;left:" + left + "px;";
        // 重新设置guideList的值
        guideList.splice(index, 1, obj);
        guideStyle = this.getStyle();
        this.setData({
          arrowTop,
          tipPosition,
          guideList,
          guideStyle,
        });
      } else {
        index += 1;
        this.setData({
          index,
        });
        this.getDomInfo();
      }
    },
    // 获取步骤提示的主要样式
    getStyle() {
      const { guideList, index } = this.data;
      const { width, height, left, top, style } = guideList[index];
      let newStyle = "width:" + width + "px;";
      newStyle += "height:" + height + "px;";
      newStyle += "left:" + left + "px;";
      newStyle += "top:" + top + "px;";
      newStyle +=
        "box-shadow: rgb(33 33 33 / 80%) 0px 0px 0px 0px, rgb(33 33 33 / 50%) 0px 0px 0px 5000px;";
      newStyle += style;
      return newStyle;
    },
    // 获取dom信息
    getDomInfo() {
      const { guideList, index } = this.data;
      const { el } = guideList[index];
      const query = wx.createSelectorQuery();
      setTimeout(() => {
        query.select(el).boundingClientRect();
        query.selectViewport().scrollOffset();
        query.exec( (res)=> {
          let data = res[0]; // #the-id节点的上边界坐标
          let scrollTop = res[1].scrollTop; // 显示区域的竖直滚动位置
          this.viewTips(data, scrollTop);
        });
      }, 10);
    },
    updateTourMap() {
      handleChangeTourType(this.data.stepName)
    },
    skip() {
      this.setData({
        showGuide: false,
      });
      this.updateTourMap()
    },
    // 添加上一步方法
    prev() {
      let { index } = this.data;
      if (index > 0) {
        index -= 1;
        this.setData({
          index
        });
        this.getDomInfo();
      }
    },
    // 下一步,修改显示文案
    next() {
      let { index, guideList } = this.data;
      if (index === guideList.length - 1) {
        this.setData({
          showGuide: false,
        });
        this.updateTourMap()
      } else {
        index += 1;
        this.setData({
          index,
        });
        this.getDomInfo();
      }
    },
  },
  lifetimes: {
    attached () {
      const { step } = this.properties;
      let { guideList, stepName } = this.data;
      guideList = step.guideList;
      stepName = step.name;
      this.setData({
        guideList,
        stepName,
        systemInfo: wx.getSystemInfoSync(),
      });
      const guide = wx.getStorageSync('tourMap');
      if (!guide || !guide[step.name]) {
        this.getDomInfo();
      } else {
        this.setData({
          showGuide: false,
        });
      }
    },
    detached () {
      // 在组件实例被从页面节点树移除时执行
    },
  },
});

miniprogram/components/tour/index.scss 

/* wxss */
.v-model {
  position: fixed;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  z-index: 1000;
}
.guide {
  z-index: 1001;
}
.guide-box {
  position: absolute;
  z-index: 10001;
  transition: all 0.2s;
  
}
.guide-box::before {
  content: '';
  height: 100%;
  width: 100%;
  border: 1px dashed #fff;
  border-radius: 8rpx;
  position: absolute;
  top: -8rpx;
  left: -8rpx;
  padding: 7rpx;
}
.arrow {
  height: 20rpx;
  width: 20rpx;
  background: #1cbbb4;
  position: absolute;
  top: 144rpx;
  left: 45%;
  transform: rotate(45deg);
}
.tips {
  width: 400rpx;
  background: linear-gradient(180deg, #1cbbb4, #0081ff);
  box-shadow: 0px 2px 9px 0px rgba(0, 0, 0, 0.1);
  color: #fff;
  position: absolute;
  top: 152rpx;
  left: -50%;
  padding: 15rpx 20rpx;
  font-size: 28rpx;
  border-radius: 12rpx;
}
.tool-btn {
   display: flex;
   justify-content: space-between;
   align-items: center;
   padding-right: 0rpx;
   margin-top: 20rpx;
}

/* 新增样式 */
.step-indicator {
  display: flex;
  justify-content: flex-end;
  margin-top: 10rpx;
}
.step-text {
  font-size: 24rpx;
  color: rgba(255, 255, 255, 0.8);
}
.btn-group {
  display: flex;
  align-items: center;
}
.prev {
  background: rgba(255, 255, 255, 0.2);
  height: 48rpx;
  width: 100rpx;
  text-align: center;
  border-radius: 8rpx;
  color: #fff;
  line-height: 48rpx;
  font-size: 24rpx;
  margin-right: 10rpx;
}
.next {
   background: #fff;
   height: 48rpx;
   width: 100rpx;
   text-align: center;
   border-radius: 8rpx;
   color: #666;
   line-height: 48rpx;
   font-size: 24rpx
}

miniprogram/components/tour/index.json 

{
  "component": true,
  "usingComponents": {}
}

miniprogram/utils/util.ts

/** 修改本地的Tour状态 */
export const handleChangeTourType = (tourName: string) => {
  const tourMap = wx.getStorageSync('tourMap') || {};
  tourMap[tourName] = true;
  wx.setStorageSync('tourMap', tourMap);
};

部分转载:微信小程序首次进入引导提示自定义组件-CSDN博客


http://www.kler.cn/a/586693.html

相关文章:

  • 农作物病害数据集
  • 性能优化:javascript 如何检测并处理页面卡顿
  • A SURVEY ON POST-TRAINING OF LARGE LANGUAGE MODELS——大型语言模型的训练后优化综述——第2部分
  • 模型评估指标详解:分类与回归场景
  • 基于SpringBoot+Vue的毕业论文管理系统+LW示例参考
  • 【NLP】 5. Word Analogy Task(词类比任务)与 Intrinsic Metric(内在度量)
  • 微信小程序面试内容整理-JSON
  • 【c++】【线程】【信号量】三个线程顺序打印1--100
  • docker pull 镜像问题
  • 【Rust交叉编译】在x86_64架构下交叉编译aarch64-linux-musl版的rust-opencv
  • 面向工业与汽车领域的高安全可靠MCU——AS32X601系列芯片解析
  • 斯坦福:通过认知行为改进LLM推理
  • java设计模式面试题3道
  • 高级java每日一道面试题-2025年2月18日-数据库篇-MySQL 如何做到高可用方案?
  • Java开发之数据库应用:记一次医疗系统数据库迁移引发的异常:从MySQL到PostgreSQL的“dual“表陷阱与突围之路
  • 串排序(信息学奥赛一本通-2048)
  • 【虚幻C++笔记】引擎源码下载及编译步骤
  • Let’s Build AI- 实用AI导航网站
  • 正则表达式全解析 + Java常用示例
  • 多线程到底重不重要?