移动端项目笔记(三)--遇到的问题,解决思路以及知识点

95 阅读6分钟

1. 颜色渐变

使用 background-image

 background-image: linear-gradient(90deg, #fa8c1d, #fcaf3f);

效果:

image.png 路由携带参数: image.png

2. 设置横向滚动

CSS3 overflow-x 属性 如果它溢出了元素的内容区-剪辑div元素的左/右边缘内容:

语法:overflow-x: visible|hidden|scroll|auto|no-display|no-content;

描述
visible不裁剪内容,可能会显示在内容框之外。
hidden裁剪内容 - 不提供滚动机制。
scroll裁剪内容 - 提供滚动机制。
auto如果溢出框,则应该提供滚动机制。
no-display如果内容不适合内容框,则删除整个框。
no-content如果内容不适合内容框,则隐藏整个内容。

3. flex-shrink

flex-shrink 属性指定了 flex 元素的收缩规则,默认值是1。在flex 元素的默认宽度之和大于容器的宽度时候,元素会发生收缩,其收缩的大小的依据是 flex-shrink 值。

image.png

4.隐藏横向的滚动条

image.png

5. 多页数据如何请求

5.1 如何监听页面滚动到底部

  1. 页面什么时候会发生滚动?
  • windows窗口滚动
  • 还是元素:overflow-y:auto

image.png

  1. document.documentElement.scrollTop文档滚动距离 image.png
  2. 当文档滚动距离加上客户端高度大于等于可滚动的距离,页面发生滚动 clientHeight+ scrollTop >= scrollHeight
// 监听window创建的滚动
window.addEventListener("scroll", () => {
    // 当前客户端的高度
    const clientHeight = document.documentElement.clientHeight;
    // 文档滚动距离
    const scrollTop = document.documentElement.scrollTop;
    // 文档可以滚动的区域高度
    const scrollHeight = document.documentElement.scrollHeight;
    console.log(clientHeight,scrollTop, scrollHeight);
})
  1. 最终实现思路

image.png 5. 优化请求,封装hooks,多个页面需要用到滚动请求数据,所以需要抽取出来。

  • 监听window创建的滚动
  • 当我们离开页面时,需要移除监听
  • 别的页面也进行类似的监听,会重复编写代码

思路一:传入回调函数

image.png

import { onMounted, onUnmounted, onActivated } from 'vue'
export default function useScroll(reachBottomCallBack) {
    // 监听window创建的滚动
    // 1. 当我们离开页面时,需要移除监听
    // 2. 别的页面也进行类似的监听,会重复编写代码
    const scrollListenerHandler = () => {
        // 当前客户端的高度
        const clientHeight = document.documentElement.clientHeight;
        // 文档滚动距离
        const scrollTop = document.documentElement.scrollTop;
        // 文档可以滚动的区域高度
        const scrollHeight = document.documentElement.scrollHeight;
        if (clientHeight + Math.ceil(scrollTop) >= scrollHeight) {
            console.log("滚动到底部了");
            if (reachBottomCallBack) {
                reachBottomCallBack();
            }
        }
    }

    // 挂载时,添加监听
    onMounted(() => {
        window.addEventListener("scroll", scrollListenerHandler)
    });
    // 活跃时
    onActivated(() => {
        window.addEventListener("scroll", scrollListenerHandler)
    })
    // 卸载时,移除监听
    onUnmounted(() => {
        window.removeEventListener("scroll", scrollListenerHandler)
    })

}

思路二:监控变量,更好的管理hooks内部的状态。

image.png

6. 页面文档滚动距离,显示搜索栏

思路一:监听文档的滚动

image.png 思路二:计算属性

// 直接使用计算属性,大于等于100为true
// 计算属性,依赖于响应式的数据,数据一旦发生变化,就会重新计算。
const isShowBar = computed(() => {
    return scrollTop.value >= 100
})

7. 页面函数执行频率太高,防抖,节流

  1. 防抖:在执行事件时,又触发了这个事件,把这个事件不断地向后延后。如果一直有执行,则会一直向后延后。
  2. 节流:在单位的时间之内,执行函数的时间非常频繁,但是在规定的时间里面,只执行函数一次。

7.1 underscore使用第三方库

  1. 安装
npm install underscore
  1. 引入使用

image.png

8. tree shaking

  1. 像webpack,vite,只会把你引入的包做一个打包,没有引入的不会做一个打包。

  2. Tree Shaking 指的就是当我引入一个模块的时候,我不引入这个模块的所有代码,我只引入我需要的代码,这就需要借助 webpack 里面自带的 Tree Shaking 这个功能来帮我们实现。

  3. 官方有标准的说法:Tree-shaking的本质是消除无用的js代码。无用代码消除在广泛存在于传统的编程语言编译器中,编译器可以判断出某些代码根本不影响输出,然后消除这些代码,这个称之为DCE(dead code elimination)

  4. 在 webpack 项目中,有一个入口文件,相当于一棵树的主干,入口文件有很多依赖的模块,相当于树枝。实际情况中,虽然依赖了某个模块,但其实只使用其中的某些功能。通过 Tree-Shaking,将没有使用的模块摇掉,这样来达到删除无用代码的目的。

9. 监听路由改变,找到对应的索引

image.png

10.加载过渡动画

思路:

  1. 将loading组件放在app.vue中
  2. loading组件使用v-if,通过判断store中isloading的状态,进行判断
  3. loading的状态是通过各个组件发送网络请求的时候进行更改

image.png

  1. 在每次发送网络请求的时候,都需要将isloading的状态设置为true,结束的时候,设置为false。
  • 思路一(不推荐,容易造成逻辑混乱) image.png
  • 思路二: image.png

11. 监听单个item的点击

image.png

  1. 给组件绑定点击事件,传递data的值。逻辑如下图所示

image.png 2. 写组件,配置路由,传递参数

image.png

12 .集成百度地图

  1. 登录百度地图开放平台,填写资料,进行开发者认证。
  2. 认证成功后,显示下面的界面。 image.png
  3. 创建应用

image.png 4. 类型选择

image.png 5. 白名单

image.png 6. 提交创建成功

image.png 7. 添加API文件

image.png

<script type="text/javascript" src="https://api.map.baidu.com/api?v=1.0&&type=webgl&ak=您的密钥">
</script>

image.png

  1. 代码思路 image.png

实现效果:

image.png 完整代码:

<template>
    <div class="map">
        <detail-section title="位置周边" more-text="查看更多周边信息">
            <div class="baidu" ref="mapRef"></div>
        </detail-section>

    </div>
</template>
<script setup>
import DetailSection from "@/components/detail-section/detail-section.vue";
import { onMounted, ref } from "vue";
const props = defineProps({
    position: {
        type: Object,
        default: () => ({})
    }
})
const mapRef = ref()
// 放在onMounted里面确保生命,容器元素存在
onMounted(() => {
    const map = new BMapGL.Map(mapRef.value);          // 创建地图实例 
    const point = new BMapGL.Point(props.position.longitude, props.position.latitude);  // 创建点坐标,经纬度 
    map.centerAndZoom(point, 15);                 // 初始化地图,设置中心点坐标和15表示地图级别
    const marker = new BMapGL.Marker(point);        // 创建标注   
    map.addOverlay(marker);                     // 将标注添加到地图中
})
</script>
<style lang="less" scoped>
.baidu {
    height: 300px;
}
</style>

13. 顶部tab的集成,页面元素的滚动

界面滚动到一定位置显示导航

image.png 实现思路:根据当前滚动的距离来控制tab的显示和隐藏。

image.png hooks useScroll函数:

import { onMounted, onUnmounted, onActivated, ref } from 'vue'
import { throttle } from 'underscore'

export default function useScroll(elRef) {
    let el = window;//设置默认为window

    // 通过变量判断是否达到底部
    const clientHeight = ref(0)
    const isReachBottom = ref(false);
    const scrollTop = ref(0);
    const scrollHeight = ref(0)
    // 监听window创建的滚动
    // 1. 当我们离开页面时,需要移除监听
    // 2. 别的页面也进行类似的监听,会重复编写代码

    // 函数执行频繁,采用防抖,节流
    const scrollListenerHandler = throttle(() => {
        // 当前滚动的是浏览器
        if (el === window) {
            // 当前客户端的高度
            clientHeight.value = document.documentElement.clientHeight;
            // 文档滚动距离
            scrollTop.value = document.documentElement.scrollTop;
            // 文档可以滚动的区域高度
            scrollHeight.value = document.documentElement.scrollHeight;
        } else {//当前滚动的元素
            // 当前元素的高度
            clientHeight.value = el.clientHeight;
            // 文档滚动距离
            scrollTop.value = el.scrollTop;
            // 文档可以滚动的区域高度
            scrollHeight.value = el.scrollHeight;
            console.log( clientHeight.value, scrollTop.value,  scrollHeight.value);
        }

        if (clientHeight.value + Math.ceil(scrollTop.value) >= scrollHeight.value) {
            console.log("滚动到底部了");
            isReachBottom.value = true;
        }
    }, 100)//间隔100毫秒

    // 挂载时,添加监听
    onMounted(() => {
        if (elRef) {
            el = elRef.value;
        }
        el.addEventListener("scroll", scrollListenerHandler)
    });
    // 活跃时
    onActivated(() => {
        el.addEventListener("scroll", scrollListenerHandler)
    })
    // 卸载时,移除监听
    onUnmounted(() => {
        el.removeEventListener("scroll", scrollListenerHandler)
    })
    // 将变量返回出去
    return { isReachBottom, scrollTop, scrollHeight };

}

14. tab点击到达对应位置

实现效果

router.gif

14.1方法一:

注意问题:页面每次滚动的时候,会引起页面的刷新,使用v-memo性能优化,数据变化的的时候,页面才会刷新。

实现思路:

image.png 实现代码如下: 子组件:

<template>
    <div class="tab-control">
        <template v-for="(item, index) in titles" :key="index">
            <div class="tab-control-item" :class="{ active: currentIndex == index }" @click="itemClick(index)">

                <!-- 2.子组件的值传递给父组件 -->
                <slot :itemsss="item" abc="cba">
                    <!-- 1.如果要将item传递到外部元素所在,使用作用插槽-->
                    <span> {{ item }}</span>
                </slot>

            </div>
        </template>
    </div>
</template>
<script>

export default {
    emits: ['itemClick'],
    props: {
        titles: {
            type: Array,
            default: () => []
        }
    },
    data() {
        return {
            currentIndex: 0,
        }
    },
    methods: {
        itemClick(index) {
            // console.log(index);
            this.currentIndex = index;
            this.$emit("itemClick", index);
        }
    }
}
</script>
<style scoped>
.tab-control {
    display: flex;
    height: 44px;
    line-height: 44px;
    text-align: center;
}

.tab-control-item {
    flex: 1;
}

.tab-control-item.active {
    color: var(--primary-color);
    font-weight: 700;

}

.tab-control-item.active span {
    border-bottom: 2px solid #ff9854;
    padding: 8px;
}
</style>

父组件:

<template>
    <div class="detail hidetabbar" ref="detailRef">
        <tab-control class="tabs" v-if="showTabControl" :titles="['描述', '设施', '房东', '评论', '须知', '地图']"
            @itemClick="tabClick" />
        <van-nav-bar title="房屋详情" left-text="返回" left-arrow @click-left="onClickLeft" />
        <div class="main" v-if="mainPart" v-memo="[mainPart]">
            <detail-swipe :swipe-data="mainPart?.topModule.housePicture.housePics"></detail-swipe>
            <detail-infos :ref="getSectionRef" :top-infos="mainPart.topModule"></detail-infos>
            <detail-facility :ref="getSectionRef"
                :house-facility="mainPart.dynamicModule.facilityModule.houseFacility"></detail-facility>
            <detail-landord :ref="getSectionRef" name="房东"
                :landlord="mainPart.dynamicModule.landlordModule"></detail-landord>
            <detail-comment :ref="getSectionRef" name="评论" :comment="mainPart.dynamicModule.commentModule" />
            <detail-notice :ref="getSectionRef" name="须知" :order-rules="mainPart.dynamicModule.rulesModule.orderRules" />
            <detail-map :ref="getSectionRef" :position="mainPart.dynamicModule.positionModule"></detail-map>
            <DetilInfo :price-intro="mainPart.introductionModule"></DetilInfo>
        </div>
        <div class="footer">
            <img src="@/assets/img/detail/icon_ensure.png" alt="">
            <div class="text">探寻自己的旅途</div>
        </div>
    </div>
</template>
<script setup>
import { ref, computed } from 'vue';
import { useRouter, useRoute } from 'vue-router'
import { getDetailInfo } from '@/service/index'
import useScroll from '@/hooks/useScroll'
import TabControl from '@/components/tab-control/tab-control.vue'
import DetailSwipe from './cpns/detail-01-swipe.vue'
import DetailInfos from './cpns/detail-02-infos.vue';
import DetailFacility from './cpns/detail-03-facility.vue';
import DetailLandord from './cpns/detail-04-landord.vue';
import DetailComment from './cpns/detail-05-comment.vue';
import DetailNotice from './cpns/detail-06-notice.vue';
import DetailMap from './cpns/detail-07-map.vue';
import DetilInfo from './cpns/detail-08-intro.vue';

const router = useRouter()
const route = useRoute();
const houseId = route.params.id

// 这里采用在组件中发送网络请求
const detailInfos = ref({})
const mainPart = computed(() => detailInfos.value.mainPart)
getDetailInfo(houseId).then(res => {
    detailInfos.value = res.data;
})

const sectionEls = [];
// ref绑定函数,每次绑定就会执行函数
const getSectionRef = (value) => {
    console.log("----");
    sectionEls.push(value?.$el)
    // console.log(value.$el);
    // const name = value.$el.getAttribute("name")
    // sectionEls.value[name] = value.$el
}

// 监听返回按钮的点击
const onClickLeft = () => {
    router.back();
}
// tabControl相关的操作,showTabControl写成计算属性
const detailRef = ref();
const { scrollTop } = useScroll(detailRef)
const showTabControl = computed(() => {
    return scrollTop.value >= 300;
});
const tabClick = (index) => {
    console.log("detailRef", detailRef.value.scrollTo);
    let instance = sectionEls[index].offsetTop;
    if (index != 0) {
        instance = instance - 44;
    }
    detailRef.value.scrollTo({
        top: instance,
        behavior: 'smooth',//平滑滚动
    })
}

</script>
<style lang="less" scoped>
.tabs {
    position: fixed;
    background-color: #fff;
    z-index: 9;
    left: 0;
    right: 0;
    top: 0;
}

.footer {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    height: 120px;

    img {
        width: 123px;
    }

    .text {
        margin-top: 12px;
        font-size: 12px;
        color: #7688a7;
    }
}
</style>

image.png

14.2 方法二

  1. 创建一个对象,用来存储点击元素和 跳转对应元素的对象
  2. 动态绑定ref,动态获取点击时的组件元素。

实现思路:

image.png

ps:Object.keys()的作用

let person = {name:"张三",age:25,address:"深圳",getName:function(){}}

Object.keys(person) // ["name", "age", "address","getName"]