目录
网络请求封装
swiper
items新闻列表、
home
IntersectionObserver API 使用教程
性能优化
网络请求封装
GET传参格式 www.baidu.com/info?t=0&age=18
传递当天日期返回以往日期信息
params` 是即将与请求一起发送的 URL 参数 ,必须是一个无格式对象(plain object)或 URLSearchParams 对象
import axios from './http'
//获取最新新闻 && 轮播图信息
export const NewsLatest = () => {
return axios.get('/api/news_latest')
}
//获取以往信息
export const NewsBefore = time => {
return axios.get('/api/news_before', {
params: {
time
}
})
}
用类库处理时间格式(不传为当前时间,传递字符串返回数组格式)
通过计算属性 处理数组对应月份(计算属性有缓存)
// 日期格式化
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}年{1}月{2}日 {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() {
//状态
let state = reactive({
today: formatTime(null, '{0}{1}{2}'),
})
//基于计算属性,处理时间
let time = computed(() => {
let { today } = state
let [month, day] = formatTime(today, '{1}-{2}').split('-')
let newMonth = month.slice(1, 2)
let arr = ['', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二']
return {
day,
month: arr[newMonth] + '月'
}
})
return {
...toRefs(state),
time,
}
},
}
</script>
swiper
通过useRouter(路由实例对象).push跳转 动态路由匹配
<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()
//跳转详情
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新闻列表、
通过router-link路由导航跳转
使用vant实现图片懒加载
main.js导入使用
import Vant, { Lazyload } from 'vant';
app.use(Lazyload, {
lazyComponent: true,
});
把src换为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与v-if同时使用时 v-for权重高
v-if控制元素销毁创建,v-show控制元素显示隐藏(在组件渲染完毕后想获取dom应该使用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}月{2}日') }}
</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">小主,奴家正在努力加载中...</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: []
})
//在第一次渲染之前获取数据:今天日期 ,轮播图数据, 今日新闻
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
}))
})
//加载更多
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 使用教程
语法
可以自动"观察"元素是否可见,Chrome 51+ 已经支持。由于可见(visible)的本质是,目标元素与视口产生一个交叉区,所以这个 API 叫做"交叉观察器"。
IntersectionObserver API 是异步的,不随着目标元素的滚动同步触发。
规格写明,IntersectionObserver
的实现,应该采用requestIdleCallback()
,即只有线程空闲下来,才会执行观察器。这意味着,这个观察器的优先级非常低,只在其他任务执行完,浏览器有了空闲才会执行
var io = new IntersectionObserver(callback, option);
,IntersectionObserver
是浏览器原生提供的构造函数,接受两个参数:callback
是可见性变化时的回调函数,option
是配置对象(该参数可选)。
方法
构造函数的返回值是一个观察器实例。实例的observe
方法可以指定观察哪个 DOM 节点。
如果要观察多个节点,就要多次调用这个方法。
// 开始观察
io.observe(document.getElementById('example'));
// 停止观察
io.unobserve(element);
// 关闭观察器
io.disconnect();
callback
callback
一般会触发两次。一次是目标元素刚刚进入视口(开始可见),另一次是完全离开视口(开始不可见) callback
函数的参数是一个数组
Option
IntersectionObserver
构造函数的第二个参数是一个配置对象。它可以设置以下属性。
threshold
属性决定了什么时候触发回调函数。它是一个数组,每个成员都是一个门槛值,默认为[0]
,即交叉比例(intersectionRatio
)达到0
时触发回调函数。
new IntersectionObserver(
entries => {/* ... */},
{
threshold: [0, 0.25, 0.5, 0.75, 1]
}
);
用户可以自定义这个数组。比如,[0, 0.25, 0.5, 0.75, 1]
就表示当目标元素 0%、25%、50%、75%、100% 可见时,会触发回调函数。
root 属性,rootMargin 属性
很多时候,目标元素不仅会随着窗口滚动,还会在容器里面滚动(比如在iframe
窗口里滚动)。容器内滚动也会影响目标元素的可见性,
IntersectionObserver API 支持容器内滚动。root
属性指定目标元素所在的容器节点(即根元素)。注意,容器元素必须是目标元素的祖先节点
var opts = {
root: document.querySelector('.container'),
rootMargin: "500px 0px"
};
var observer = new IntersectionObserver(
callback,
opts
);
上面代码中,除了root
属性,还有rootMargin属性。后者定义根元素的margin
,用来扩展或缩小rootBounds
这个矩形的大小,从而影响intersectionRect
交叉区域的大小。它使用CSS的定义方法,比如10px 20px 30px 40px
,表示 top、right、bottom 和 left 四个方向的值。
这样设置以后,不管是窗口滚动或者容器内滚动,只要目标元素可见性变化,都会触发观察器。
性能优化
vue默认做了对象的深层级劫持 ,内层数据不需要修改的情况下使用 Object.freeze冻结就不会劫持
在组件销毁时清除定时器,监听器
使用vant组件库实现图片懒加载