current position:Home>Actual combat of vue3 project --- Zhihu daily --- home page function

Actual combat of vue3 project --- Zhihu daily --- home page function

2022-04-29 06:49:20Front end childe Jia

Catalog

Network request encapsulation

header

swiper

items News list 、

home

IntersectionObserver API Use the tutorial

performance optimization


Network request encapsulation

GET Format of parameter transfer www.baidu.com/info?t=0&age=18

Pass the date of the current day and return the previous date information

params` Is about to be sent with the request URL Parameters , Must be an unformatted object (plain object) or URLSearchParams object

import axios from './http'

// Get the latest news  &&  Carousel map information 
export const NewsLatest = () => {
    return axios.get('/api/news_latest')
}
// Get past information 
export const NewsBefore = time => {
    return axios.get('/api/news_before', {
        params: {
            time
        }
    })
}

Use class library to deal with time format ( Do not pass to the current time , Pass string to return array format )

By calculating properties Process the month corresponding to the array ( Calculation property has cache )

//  Date formatting 
export const formatTime = function formatTime(time, template) {
    if (typeof time !== "string") {
        time = new Date().toLocaleString('zh-CN', { hour12: false });
    }
    if (typeof template !== "string") {
        template = "{0} year {1} month {2} Japan  {3}:{4}:{5}";
    }
    let arr = [];
    if (/^\d{8}$/.test(time)) {
        let [, $1, $2, $3] = /^(\d{4})(\d{2})(\d{2})$/.exec(time);
        arr.push($1, $2, $3);
    } else {
        arr = time.match(/\d+/g);
    }
    return template.replace(/\{(\d+)\}/g, (_, $1) => {
        let item = arr[$1] || "00";
        if (item.length < 2) item = "0" + item;
        return item;
    });
};
<script>


import { reactive, ref, toRefs, computed } from "vue";
import { formatTime } from "../assets/utils";
export default {
    name: "Header",

    setup() {
        // state 
        let state = reactive({
            today: formatTime(null, '{0}{1}{2}'),
        })

        // Based on calculated attributes , The processing time 
        let time = computed(() => {
            let { today } = state
            let [month, day] = formatTime(today, '{1}-{2}').split('-')
            let newMonth = month.slice(1, 2)
            let arr = ['', ' One ', ' Two ', ' 3、 ... and ', ' Four ', ' 5、 ... and ', ' 6、 ... and ', ' 7、 ... and ', ' 8、 ... and ', ' Nine ', ' Ten ', ' 11、 ... and ', ' Twelve ']
            return {
                day,
                month: arr[newMonth] + ' month '
            }
        })

        return {
            ...toRefs(state),
            time,
        }
    },
}   
</script>

swiper

adopt useRouter( Route instance object ).push Jump Dynamic routing matching

<template>
    <section class="banner-box">
        <van-swipe :autoplay="3000" lazy-render v-if="bannerList.length > 0">
            <van-swipe-item v-for="item in bannerList" :key="item.id" @click="goToDetail(item.id)">
                <img class="abbre" :src=item.image alt="">
                <div class="desc">
                    <h2 class="title">
                        {
   { item.title }}
                    </h2>
                    <p class="author">
                        {
   { item.hint }}
                    </p>

                </div>
            </van-swipe-item>
        </van-swipe>
    </section>
</template>

<script>
import { useRouter } from "vue-router";
export default {
    name: "Swiper",
    props: ['bannerList'],
    setup() {
        const router = useRouter()
        // Jump details 
        const goToDetail = (id) => router.push(`/detail/${id}`)
        return {
            goToDetail
        }
    },

}
</script>

<style lang="less" scoped>
.banner-box {
    box-sizing: border-box;
    height: 375px;
    background: #eee;
    overflow: hidden;

    .van-swipe {
        height: 100%;
        overflow: hidden;

        .abbre {
            display: block;
            width: 100%;
            min-height: 100%;
        }
    }

    .desc {
        position: absolute;
        bottom: 0;
        left: 0;
        z-index: 10;
        box-sizing: border-box;
        padding: 15px 20px;
        width: 100%;
        background: rgba(0, 0, 0, .4);
        background: -webkit-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, .4));
        color: #fff;

        .title {
            line-height: 25px;
            font-size: 18px;
            max-height: 50px;
            overflow: hidden;
            display: -webkit-box;
            -webkit-line-clamp: 2;
            -webkit-box-orient: vertical;
        }

        .author {
            font-size: 14px;
            line-height: 25px;
            color: rgba(255, 255, 255, .8);
        }
    }

    /deep/.van-swipe__indicators {
        left: auto;
        transform: none;
        right: 15px;

        .van-swipe__indicator {
            width: 5px;
            height: 5px;
            background: rgba(255, 255, 255, .8);
        }

        .van-swipe__indicator--active {
            width: 15px;
            background: #fff;
            border-radius: 5px;
        }
    }
}
</style>

items News list 、

adopt router-link Route navigation jump

Use vant Lazy image loading

main.js Import and use

import Vant, { Lazyload } from 'vant';

app.use(Lazyload, {

    lazyComponent: true,

});

hold src Replace with lazy

<template>
    <div class="news-box">
        <router-link :to="`/detail/${cur.id}`">
            <h3 class="title">
                {
   { cur.title }}
            </h3>
            <div class="desc"> {
   { cur.hint }}</div>
            <div class="pic">
                <img class="abbre" v-if="cur.images && cur.images.length > 0" v-lazy='cur.images[0]' alt="">
            </div>
        </router-link>
    </div>
</template>
        
<script>
export default {
    name: "NewsItem",
    props: {
        cur: {
            type: Object,
            require: true
        }
    },
    setup() { },
}

</script>

<style lang="less" scoped>
.news-box {
    position: relative;

    a {
        display: block;
        padding-right: 90px;
        min-height: 70px;
        overflow: hidden;
        margin-top: 30px;



        .pic {
            position: absolute;
            right: 0;
            top: 0;
            width: 70px;
            height: 70px;
            background: #eee;

            img {
                display: block;
                width: 100%;
                height: 100%;
            }
        }

        .title {
            font-size: 16px;
            color: #000;
            line-height: 25px;
            max-height: 50px;
            overflow: hidden;
            display: -webkit-box;
            -webkit-line-clamp: 2;
            -webkit-box-orient: vertical;
        }

        .desc {
            line-height: 20px;
            font-size: 12px;
            color: #999;
        }
    }


}
</style>

home

v-for And v-if When used at the same time v-for Weight high

v-if Control element destruction creation ,v-show Control elements show hidden ( After the component is rendered, you want to get dom You should use v-show)

<template>
    <Header></Header>
    <Swiper :bannerList="bannerList"></Swiper>
    <van-skeleton title :row="5" v-if="newList === 0" />
    <section class="news-box " v-else>
        <div class="day-box" v-for="(item, index ) in newList" :key="item.date">
            <van-divider dashed content-position="left" v-if="index > 0">{
   { formatTime(item.date, '{1} month {2} Japan ') }}
            </van-divider>
            <div class="list">
                <NewsItem v-for="cur in item.stories" :key="cur.id" :cur="cur" />
            </div>
        </div>
    </section>
    <section class="loadmore-box" ref="loadmore" v-show="newList">
        <van-loading size="18px"> The little Lord , I'm trying to load ...</van-loading>
    </section>
</template>

<script>
import Header from '../components/header.vue'
import Swiper from '../components/swiper.vue'
import NewsItem from '../components/items.vue'
import { reactive, ref, toRefs, onBeforeMount, onMounted, onBeforeUnmount } from "vue";
import { NewsLatest, NewsBefore } from "../api/index";
import { formatTime } from "../assets/utils";

export default {
    name: "Home",
    components: {
        Header,
        Swiper,
        NewsItem
    },
    setup() {
        let state = reactive({
            today: '',
            bannerList: [],
            newList: []
        })

        // Get the data before the first rendering : Today's date  , Carousel data ,  News today 
        onBeforeMount(async () => {
            let { date, stories, top_stories } = await NewsLatest();
            state.today = date
            state.bannerList = Object.freeze(top_stories)
            state.newList.push(Object.freeze({
                date,
                stories
            }))
        })

        // Load more 
        let loadmore = ref(null)
        const ob = new IntersectionObserver(async changes => {
            let item = changes[0]
            if (item.isIntersecting) {
                let len = state.newList.length
                if (len === 0) return
                let data = await NewsBefore(state.newList[len - 1].date)
                state.newList.push(Object.freeze(data))
            }
        });
        onMounted(() => {
            if (!loadmore.value) return
            ob.observe(loadmore.value)
        })

        onBeforeUnmount(() => {
            if (!loadmore.value) return
            ob.unobserve(loadmore.value)
        })
        return {
            ...toRefs(state),
            formatTime,
            loadmore
        }
    },
};
</script>
<style lang="less" scoped>
.van-skeleton {
    padding: 15px;
}

.news-box {
    padding: 0 15px;

    .day-box {
        margin-top: 30px;
    }
}

.loadmore-box {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 50px;
    margin-top: 20px;
    background: #f4f4f4;
}
</style>

IntersectionObserver API Use the tutorial

grammar

Can be automatically " Observe " Whether elements are visible ,Chrome 51+ Has supported . Because it's visible (visible) The essence is , The target element creates an intersection with the viewport , So this API be called " Cross viewer ".

IntersectionObserver API It's asynchronous , Does not trigger synchronously with the rolling of the target element .

Specification ,IntersectionObserver The implementation of the , Should adopt requestIdleCallback(), That is, only threads are idle , To execute the observer . It means , This observer has a very low priority , Only after other tasks are completed , The browser will only execute when it is idle

var io = new IntersectionObserver(callback, option);

,IntersectionObserver It's the browser's native constructor , Take two parameters :callback Is a callback function when visibility changes ,option It's the configuration object ( This parameter is optional ).

Method

The return value of the constructor is an observer instance . Example of observe Methods can specify which to observe DOM node .

If you're looking at multiple nodes , You have to call this method many times .

//  Start to observe 
io.observe(document.getElementById('example'));

//  Stop observing 
io.unobserve(element);

//  Turn off the viewer 
io.disconnect();

callback

callback It usually triggers twice . Once the target element has just entered the viewport ( Start to see ), The other is to leave the view completely ( It's not visible at first ) callback The argument to the function is an array

Option

IntersectionObserver The second argument to the constructor is a configuration object . It can set the following properties .

threshold Property determines when the callback function is triggered . It's an array , Each member is a threshold , The default is [0], That is, the cross ratio (intersectionRatio) achieve 0 Trigger callback function .

new IntersectionObserver(
  entries => {/* ... */}, 
  {
    threshold: [0, 0.25, 0.5, 0.75, 1]
  }
);

You can customize this array . such as ,[0, 0.25, 0.5, 0.75, 1] It means when the target element 0%、25%、50%、75%、100% When visible , Will trigger the callback function .

root attribute ,rootMargin attribute

A lot of times , The target element will not only scroll with the window , And roll in the container ( For example iframe Scroll through the window ). Scrolling within the container also affects the visibility of the target element ,

IntersectionObserver API Supports scrolling in containers .root Property specifies the container node where the target element is located ( The root element ). Be careful , The container element must be the ancestor node of the target element

var opts = { 
  root: document.querySelector('.container'),
  rootMargin: "500px 0px" 
};

var observer = new IntersectionObserver(
  callback,
  opts
);

In the above code , except root attribute , also rootMargin attribute . The latter defines the root element margin, Used to expand or shrink rootBounds The size of this rectangle , Thereby affecting intersectionRect The size of the crossing area . It USES CSS How to define , such as 10px 20px 30px 40px, Express top、right、bottom and left Values in four directions .

After this setting , Whether it's window scrolling or container scrolling , As long as the visibility of the target element changes , Will trigger the observer .

performance optimization

vue By default, the object is hijacked at the deep level , The inner data can be used without modification  Object.freeze Freezing won't hijack

Clear the timer when the component is destroyed , Monitor

Use vant The component library realizes lazy loading of pictures

copyright notice
author[Front end childe Jia],Please bring the original link to reprint, thank you.
https://en.qdmana.com/2022/116/202204260850228629.html

Random recommended