33、实现全屏展示功能
我们知道在原生dom
上,提供了一些方法来供我们开启或关闭全屏:
Element.requestFullscreen()
Document.exitFullscreen()
Document.fullscreen
Document.fullscreenElement
一般浏览器
使用requestFullscreen()
和exitFullscreen()
来实现
早期版本Chrome浏览器
基于WebKit内核的浏览器需要添加webkit
前缀,使用webkitRequestFullScreen()
和webkitCancelFullScreen()
来实现。
早期版本IE浏览器
基于Trident内核的浏览器需要添加ms
前缀,使用msRequestFullscreen()
和msExitFullscreen()
来实现,注意方法里的screen的s为小写形式。
早期版本火狐浏览器
基于Gecko内核的浏览器需要添加moz
前缀,使用mozRequestFullScreen()
和mozCancelFullScreen()
来实现。
早期版本Opera浏览器
Opera浏览器需要添加o
前缀,使用oRequestFullScreen()
和oCancelFullScreen()
来实现。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>萌狼蓝天 伴姬一生</title>
</head>
<body>
<div>
<img src="./source/img/dog.jpg" height="300" alt="">
<button id="full">全屏显示</button>
<button id="cancelFull">取消全屏</button>
<button id="isFull">是否全屏</button>
<p id="tip" style="color:blue"></p>
</div>
<script>
//全屏显示
var div = document.querySelector('div');
document.querySelector('#full').onclick = function () {
if (div.requestFullscreen) {
div.requestFullscreen(); // 正常浏览器
} else if (div.webkitRequestFullScreen) {
div.webkitRequestFullScreen(); // webkit
} else if (div.mozRequestFullScreen) {
div.mozRequestFullScreen(); //早期火狐浏览器
} else if (div.oRequestFullScreen) {
div.oRequestFullScreen(); //早期Opera浏览器
} else if (div.msRequestFullscreen) {
div.msRequestFullscreen(); //早期IE浏览器
} else {
alert('暂不支持在您的浏览器中全屏');
}
};
//取消全屏显示
document.querySelector('#cancelFull').onclick = function () {
if (document.exitFullscreen) {
document.exitFullscreen(); // 正常浏览器
} else if (document.webkitCancelFullScreen) {
document.webkitCancelFullScreen(); // webkit
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen(); //早期火狐浏览器
} else if (document.oCancelFullScreen) {
document.oCancelFullScreen(); //早期Opera浏览器
} else if (document.msCancelFullscreen) {
document.msCancelFullscreen(); //早期IE浏览器
} else {
alert('暂不支持在您的浏览器中全屏');
}
//可以用document,也可以用上方设置的变量 div
};
//检测当前是否处于全屏状态
document.querySelector('#isFull').onclick = function () {
// alert(document.webkitIsFullScreen); // webkit
// 使用上面的弹窗方式。如果是处于全屏状态,会自动退出
document.getElementById('tip').innerHTML=document.webkitIsFullScreen;
};
</script>
</body>
</html>
但是这些方法:在一些低版本浏览器中存在兼容性的问题,需要我们手动封装;如果不想封装的话也可以使用第三方封装好的库来处理:
常见的第三方全屏库:
- 1、
vueUse
import {
useFullscreen } from '@vueuse/core'
const imgEle = ref(null)
const {
isFullscreen, enter, exit, toggle } = useFullscreen(imgEle)
const handleFullScreen = () => {
imgEle.value.style.backgroundColor = 'transparent'
enter()
}
34、从首页跳转到详情页解决方案
34.1、需求分析
首先我们看一下首页的图片
分析:
- 当点击某一个图片时、跳转到对应图片的详情页
- 并且在跳转的过程中有从小到放大的动画的效果(类似于全屏效果的动画)
34.2、分析现阶段路由跳转动画
在vue-router页面跳转如果要实现跳转到动画,需要借助于transition组件来进行实现动画
<router-view v-slot="{ Component, route }">
<transition name="fade">
<component :is="Component" />
</transition>
</router-view>
这是在vue官网截的图,从图中我们可以得知transition组件一般适用于 组件 或 元素的显示和隐藏、并不适合我们的需求、
34.3、提出解决方案
那么根据咱们上一小节的分析,我们知道通过 vue-router
的过渡动效是无法实现咱们期望的路由切换效果的。
那么我们应该如何去做呢?
想要搞明白咱们的可行性方案,那么首先我们得先来搞清楚什么是路由的跳转?
所谓路由的跳转无非指的是两个部分:
-
1.浏览器的url 发生了变化
-
2.浏览器中展示的页面组件发生了变化
那么只要满足这两点,我们就认为路径进行了跳转
所以说,我们是不是可以换个思路,我们不去进行真实的路由跳转,而是先修改浏览器的URL,再切换展示的页面(以组件的形式覆盖整个浏览器可视区域)。这样对于用户而言,是不是就完成了整个的路由跳转工作。
所以说我们的具体问题就变成了:
- 1.如何让浏览器的url发生变化,但是不跳转页面
- 2.如何让一个新的组件以包含动画的形式进行展示
- 那么想要完成第一个功能我们可以使用:
History.pushState()
方法 - 而第二个功能我们可以使用
GSAP
这个动画库来进行实现。
- 那么想要完成第一个功能我们可以使用:
34.4、关于GSAP介绍
GSAP, 它是一个非常强大的js动画库, 他支持Flip
、滚动动画等;在其内部给我们提供了非常多的方法供我们来使用;
本次我们使用到的api
,只有set
和to
两个:
-
set
: 给元素设置初始化(动画执行之前)的属性 -
to
: 给元素设置结束时(动画之后结束)的属性-
to方法的返回值为
tween
对象、我们通过调用对应的api来控制元素动画的开启、暂停、翻转、重新开始…tween.play() tween.pause() tween.resume() tween.reverse() tween.restart()
-
测试1 - 自动执行动画
<template>
<div class="w-screen h-[400px] flex items-center justify-center">
<div ref="testGsap" class="border border-zinc-300 rounded-sm p-4">test GSAP</div>
</div>
</template>
<script setup>
import gsap from "gsap"
import { onMounted, ref } from 'vue'
const testGsap = ref(null)
onMounted(() => {
gsap.set(testGsap.value, { transform: 'translateX(-100px)', color: 'blue' })
gsap.to(testGsap.value, { transform: 'translateX(100px)', color: 'pink', duration: 1, delay: 0 })
})
</script>
测试2 - 手动控制执行动画
<template>
<div class="w-screen h-[400px] flex items-center justify-center">
<div ref="testGsap" class="border border-zinc-300 rounded-sm p-4">test GSAP</div>
</div>
<Button @click="handleStart">执行动画</Button>
<Button @click="handleReverse">翻转动画</Button>
</template>
<script setup>
import gsap from "gsap"
import { onMounted, ref } from 'vue'
const testGsap = ref(null)
let tween
onMounted(() => {
gsap.set(testGsap.value, { transform: 'translateX(-100px)', color: 'blue' })
tween = gsap.to(testGsap.value, { transform: 'translateX(100px)', color: 'pink', duration: 1, delay: 0 })
tween.pause();
})
const handleStart = () => {
tween.play()
}
const handleReverse = () => {
tween.reverse()
}
</script>
也就是当我们不主动暂停的话,
gsap.to
函数调用之后就会开始执行动画
34.5、实现从首页调到详情页
-
1、创建pins/components/pins.vue组件
-
2、在首页中使用
Pins
组件,并使用translation
包裹、并设置执行动画<transition :css="false" @before-enter="onBeforeEnter" @enter="onEnter" @after-enter="onAfterEnter" @leave="onLeave" @after-leave="onAfterLeave" > <Pins :id="currentItem.id" v-if="pinsVisible"/> </transition>
-
3、点击每一项时,计算当前项距离屏幕左边和边的距离、并利用h5的
pushState
改变地址栏路径 -
4、展示
Pins组件
, 在展示过程中在过渡钩子函数中设置对应的动画样式 -
5、当需要关闭Pins组件时; 我们需要监听页面的回退事件
popState
,当时间被调用时关闭Pins组件
先看下我们要实现的效果
开始实现
list/index.js
<template>
<div class="w-full">
...
<!-- 图片详情 -->
<transition
:css="false"
@before-enter="onBeforeEnter"
@enter="onEnter"
@after-enter="onAfterEnter"
@leave="onLeave"
@after-leave="onAfterLeave"
>
<Pins :id="currentItem.id" v-if="pinsVisible"/>
</transition>
</template>
<script setup>
import ListItem from './item/index.vue'
import { getPexels } from '@/api/pexels'
import { isMoboleTerminal } from '@/utils/flexible'
import { ref, watch, computed } from 'vue'
import { useStore } from 'vuex'
import Pins from '@/views/pins/components/pins.vue'
import gsap from 'gsap'
import { useEventListener } from '@vueuse/core'
const store = useStore()
// 选中item
const selectItem = (item) => {
currentItem.value = item
// 修改页面地址
window.history.pushState(null, '', '/pins/' + item.id)
}
// 监听页面回退
useEventListener('popstate', () => {
delete currentItem.value.id
})
const pinsVisible = computed(() => currentItem.value.id !== void 0)
// pins动画钩子 -- 动画执行之前
const onBeforeEnter = (el) => {
gsap.set(el, {
scaleX: 0.2,
scaleY: 0.2,
transformOrigin: '0 0',
translateX: currentItem.value.translateX,
translateY: currentItem.value.translateY,
opacity: 0
})
}
// pins动画钩子 -- 动画执行过程
const onEnter = (el, done) => {
el.__gsap__ = gsap.to(el, {
duration: 0.4,
scaleX: 1,
scaleY: 1,
transformOrigin: '0 0',
translateX: 0,
translateY: 0,
opacity: 1,
onComplete: done
})
}
// pins动画钩子 -- 动画离开过程
const onLeave = (el, done) => {
el.__gsap__.reverse()
setTimeout(() => {
done()
}, el.__gsap__._dur * 1500)
}
const onAfterLeave = (el) => {
currentItem.value = {}
}
</script>
item.vue
const handleSelectItem = () => {
// 获取图片中间路基浏览器左边和顶部的距离
const {
left, top, width, height } = imgEle.value?.getBoundingClientRect()
const translateX = left + width / 2
const translateY = top + height / 2
emits('selectItem', {
...props.pexel,
translateX,
translateY
})
}
34.5、解决刷新丢失的问题 - 路由props传参
所谓的刷新丢失,就是刷新之后、会直接访问我们设置的路径、而路径没有没有匹配到对应的路由组件、所以就会显示空白页面;
所以,我们的思路是:
方案1:
- 1、在路由表中配置对应连接的路由对象
- 2、路由对象中的组件中使用到我们上面定义的pins.vue组件
- 3、这样刷新时就会通过路由匹配到对应的路由组件,在路由初始化时获取
id
参数传递给组件
方案2:路由props传参
vue-router 中 props传参给组件、
在你的组件中使用 $route
会与路由紧密耦合,这限制了组件的灵活性,因为它只能用于特定的 URL。虽然这不一定是件坏事,但我们可以通过 props
配置来解除这种行为:
我们可以将下面的代码
const User = {
template: '<div>User {
{ $route.params.id }}</div>'
}
const routes = [{
path: '/user/:id', component: User }]
替换成
const User = {
// 请确保添加一个与路由参数完全相同的 prop 名
props: ['id'],
template: '<div>User {
{ id }}</div>'
}
const routes = [{
path: '/user/:id', component: User, props: true }]
这允许你在任何地方使用该组件,使得该组件更容易重用和测试。
本案例中我们使用路由props传参
export default [
{