拼搏30天VUE.js之 条件渲染(v-if 和 v-show)(Part5)

本文深入解析Vue中的v-if和v-show指令,阐述两者在条件渲染中的不同应用与性能考量,帮助开发者理解何时选择何种指令以优化用户体验。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

PS:在Vue中,我们可以使用v-ifv-show来控制元素或模板的渲染。而v-ifv-show也属于Vue的内部常用的指令。这里所说的指令就是directive,指特殊的带有前缀v-的命令,指令的值限定为绑定表达式,指令的职责是当表达式的值改变时把某些特殊的行为应用到DOM上。

咱们先来看v-if指令。其作用就是根据表达式的值truefalse在DOM中生成或者移除一个元素(或多个元素)。有点类似JavaScript中的if条件判断。在Vue中除了v-if也有v-else-ifv-else之类。
轉載自大漠《Vue 2.0学习笔记:v-if 和 v-show》一文: https://www.w3cplus.com/vue/v-if-vs-v-show.html © w3cplus.com

* v-if

1-1:v-if="true"

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>v-if和v-show</title>
<script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script>
</head>
<body>
<div id="app">
    <p v-if="true">我出現了</p>
</div>
    
<script>
new Vue({
  el: '#app',
  data: {
  }
})
</script>
</body>
</html>

結果為:

我出現了

1-2:v-if="XX"

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Vue 测试实例 - 菜鸟教程(runoob.com)</title>
<script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script>
</head>
<body>
<div id="app">
    <p v-if="show">能看到我嗎?</p>
</div>   
<script>
new Vue({
  el: '#app',
  data: {
    show: true
  }
})
</script>
</body>
</html>

結果為:

能看到我嗎?

1-3:template v-if = "true"

我们切换多个元素一般不这么使用,而是配合<template>元素当做包装元素,并在上面使用v-if。最终的渲染结果不会包含<template>元素。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>v-if和v-show</title>
<script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script>
</head>
<body>
<div id="app">
   <template v-if = "true">
	   <p>我在這裡1</p>
	   <p>我在這裡2</p>
	   <p>我在這裡3</p>
	</template> 
</div>
    
<script>
new Vue({
  el: '#app',
  data: {
  }
})
</script>
</body>
</html>

結果為:

我在這裡1

我在這裡2

我在這裡3

1-4:  template v-if = "XX"

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>v-if和v-show</title>
<script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script>
</head>
<body>
<div id="app">
   <template v-if = "show">
	   <p>我在這裡1</p>
	   <p>我在這裡2</p>
	   <p>我在這裡3</p>
	</template> 
</div>
    
<script>
new Vue({
  el: '#app',
  data: {
	  show:true
  }
})
</script>
</body>
</html>

結果為:

我在這裡1

我在這裡2

我在這裡3

2-1:v-else為false

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Vue 测试实例 - 菜鸟教程(runoob.com)</title>
<script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script>
</head>
<body>
<div id="app">
    <p v-if="show">能看到我嗎?1</p>
	  <p v-else="hide">能看到我嗎?2</p>
</div>   
<script>
new Vue({
  el: '#app',
  data: {
    show: true,
	  hide:false
  }
})
</script>
</body>
</html>

結果為:

能看到我嗎?1

2-2:v-else為true

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Vue 测试实例 - 菜鸟教程(runoob.com)</title>
<script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script>
</head>
<body>
<div id="app">
    <p v-if="show1">能看到我嗎?1</p>
	  <p v-else="show2">能看到我嗎?2</p>
</div>   
<script>
new Vue({
  el: '#app',
  data: {
    show1: false, 
	  show2: true
  }
})
</script>
</body>
</html>

結果為:

能看到我嗎?2

3-1:v-else-if

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Vue 测试实例 - 菜鸟教程(runoob.com)</title>
<script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script>
</head>
<body>
<div id="app">
    <p v-if="show == 1">能看到我嗎?1</p>
	<p v-else-if="show==2">能看到我嗎?2</p>
	  <p v-else="show==3">能看到我嗎?3</p>
</div>   
<script>
new Vue({
  el: '#app',
  data: {
    show: 3
  }
})
</script>
</body>
</html>

結果為:

能看到我嗎?3

4-1:v-show

v-showv-if功能有点相似,其中v-if依赖于控制DOM节点,而v-show是依赖于控制DOM节点的display属性。当v-show传入的值为true时,对应DOM元素的display的值为block之类的,反之为false时,display的值为none。也就是用户看不到元素的显示,但其DOM元素还是存在的。原文: https://www.w3cplus.com/vue/v-if-vs-v-show.html © w3cplus.com

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Vue 测试实例 - 菜鸟教程(runoob.com)</title>
<script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script>
</head>
<body>
<div id="app">
    <p v-show="true">能看到我嗎?1</p>
	<p v-show="hide">能看到我嗎?2</p>
</div>   
<script>
new Vue({
  el: '#app',
  data: {
    hide: false
  }
})
</script>
</body>
</html>

結果如下:

能看到我嗎?1

注意,v-show 不支持 <template> 语法,也不支持 v-else
原文: https://www.w3cplus.com/vue/v-if-vs-v-show.html © w3cplus.com

v-if vs. v-show

在切换 v-if 块时,Vue.js 有一个局部编译/卸载过程,因为 v-if 之中的模板也可能包括数据绑定或子组件。

v-if 是真实的条件渲染,因为它会确保条件块在切换当中合适地销毁与重建条件块内的事件监听器和子组件。

v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——在条件第一次变为真时才开始局部编译(编译会被缓存起来)。

相比之下,v-show 简单得多——元素始终被编译并保留,只是简单地基于 CSS 切换。

一般来说,v-if 有更高的切换消耗而 v-show 有更高的初始渲染消耗。

因此,如果需要频繁切换 v-show 较好,如果在运行时条件不大可能改变 v-if 较好。

轉載自W3C《Vue.js 条件渲染》一文:https://www.w3cschool.cn/vuejs/d9bu1js7.html

请生成新代码 使得webview嵌在页面内可以跟随页面移动 <template> <view class="wapper"> <!-- <view class="bg-image"></view> --> <image class="bg-image" src="/static/home/bus/bg-image.png" mode="widthFix" style="width: 100%;" /> <!-- 自定义导航栏 --> <view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }"> <view class="navbar-content"> <!-- 左侧返回按钮 --> <view class="back-btn" @click="handleBack"> <image src="/static/home/bus/back-icon.png" mode="aspectFit" style="width: 48rpx;height: 48rpx;" /> </view> <!-- 中间标题 --> <text class="title">{{busLineBase.name}}</text> </view> </view> <view class="wapper-content" :style="{ paddingTop: statusBarHeight +50 + 'px' }"> <view class="line_info"> <view class="site"> <text class="from">{{busLineBase.startSn}}</text> <view class="arrow"> <text class="to">{{busLineBase.endSn}}</text> </view> </view> <view class="tit"> <view class="time_s"> <text class="time-text">{{busLineBase.firstTime}}</text> </view> <view class="time_e"> <text class="time-text">{{busLineBase.lastTime}}</text> </view> <text class="price">票价: {{busLineBase.price}}</text> </view> </view> <view class="distance-class"> <view class="station"> <view class="station-title"> <view class="station-name">候车站</view> <view class="station-content"> <view class="active">{{stationName}}</view> <view class="underline"></view> </view> </view> </view> <view class="distance" v-if="state==0 && travelOrder!=0"> <view class="time"> <text class="time_title">{{Math.ceil(travelTime/60)}}分钟</text> </view> <text class="arr">{{travelOrder}}站/ <text v-if="travelDistance<1000">{{numFilter(travelDistance)}}m</text> <text v-else>{{numFilter(travelDistance/1000)}}km</text> </text> </view> <view class="distance" v-else-if="state==0 && travelOrder==0 && isArrive==1"> <view class="time"> <view class="bus-arr">已到站</view> </view> </view> <view class="distance" v-else-if="state==0 &&isArrive==0"> <view class="time"> <view class="bus-coming">即将到站 {{travelDistance}}m</view> </view> </view> <view class="distance" v-else-if="state==-1"> <view class="time"> <view class="none">等待发车</view> </view> </view> <view class="distance" v-else> <view class="time"> <view class="none">暂无车辆信息</view> </view> </view> <!-- <view>已到站</view> --> </view> </view> <view class="site_list"> <view> <scroll-view class="wapper_sitelist" scroll-x="true" scroll-with-animation :scroll-into-view="targetId"> <view class="road"> <view class='part' v-for="(item, index) in siteList" @click="showActive(index, item.sn)"> <view :id="'id'+item.sid" class='name'> <view> <view class="dot_p" :class="{'dot_p_show': ind === index}"></view> <view class='dot_r' v-if="index!=siteList.length-1"></view> </view> <div :class="item.state==1?'road-bus':'road-bus-coming'" v-if="item.isHaveBus==0"></div> <view class='dot_d' :class="{'dot_d_show': ind === index}"></view> <view class='text' :class="{'text_show': ind === index}"> <view>{{index +1}}</view> <view class="text-dir"> {{item.sn}} </view> </view> </view> </view> </view> </scroll-view> <!-- <view> <web-view ref="webview" :webview-styles="webviewStyles" :src="src" :fullscreen="false"></web-view> </view> --> <!-- <view class="page-body"> <view class="page-section page-section-gap"> <map style="width: 100%; height: 250px;" :latitude="latitude" :longitude="longitude" :markers="covers"> </map> </view> </view> --> </view> </view> <view> <web-view ref="webview" :webview-styles="webviewStyles" :src="src" :fullscreen="false"></web-view> </view> <!-- <iframe ref="iframe" id="iframe" src="./static/map.html" width="100%" height="300px" frameborder="0" class="map"> --> </iframe> </view> </template> <script> import { getRealTimeBusLine, getRealTimeBusLineStation } from '@/request/bus.js' export default { data() { return { src: './static/web/map.html', timer: null, isArrive: 0, targetId: '', state: 0, stationName: '', travelDistance: 0, travelTime: 0, travelOrder: 0, ratio: '', ind: '', siteId: "", lineName: "二号线", siteName: "动漫园站", siteList: [], busLineBase: [], stateBus: [], time_s: '', time_e: '', from: '', busPosition: [0, 5, 11], to: '', isActive: false, lineId: '', siteNum: '0', haveBus: true, webviewStyles: { position: 'relative', top: '580px', // bottom: '30px', zindex: '9999', // marginTop: '850px', left: '20px', right: '20px', // height:'300px' // height: '500px', }, id: 0, // 使用 marker点击事件 需要填写id title: 'map', latitude: 39.90923, longitude: 116.397428, covers: [{ latitude: 39.909, longitude: 116.39742, iconPath: '../../../static/location.png' }, { latitude: 39.90, longitude: 116.39, iconPath: '../../../static/location.png' }] } }, onLoad(options) { // 获取路由参数 const { statusBarHeight } = uni.getSystemInfoSync() // const systemInfo = uni.getSystemInfoSync(); // this.ratio = (systemInfo.windowWidth / 750) / 0.5 console.log("options", options) this.statusBarHeight = statusBarHeight this.lineId = options.lineId this.lat = options.lat this.lng = options.lng this.lineName = options.lineName this.getLineDetail() if (this.timer) { clearInterval(this.timer); this.timer = null; } // this.timer = setInterval(() => { // console.log("30") // this.refresh() // }, 1000 * 30); // this.$nextTick(function() { // 仅 App 平台执行 // amap.initMap('amap', 'cd0bbe150380e1263de209abf425745b'); this.init() // }) }, methods: { // vueiframe传递信息 init() { this.src = "../../../../hybrid/html/map.html" // var res = { // lineName:this.lineName // } // this.src = './static/map.html?data=' + encodeURIComponent(JSON.stringify({ // ...res // })) // var lineName = this.lineName // const iframeWindow = this.$refs.iframe.contentWindow; // this.dom = document.getElementById('iframe') // iframeWindow.postMessage({ // cmd: 'myVue', // params: { // info: 'Vueiframe传递的消息', // } // }, '*') }, numFilter(value) { if (value != 0) { // 将数值转换为浮点数,并保留两位小数 let realVal = parseFloat(value).toFixed(2); return realVal; } }, async getLineDetail() { var res = await getRealTimeBusLine({ lineId: this.lineId, lat: this.lat, lng: this.lng }) if (res.code == 0) { console.log("1", res) this.siteList = res.data.busInfStop var ind = res.data.targetOrder this.ind = res.data.targetOrder - 1 this.stationName = this.siteList[ind - 1].sn this.state = res.data.state this.busLineBase = res.data.busLineBase this.targetOrder = res.data.targetOrder this.$nextTick(function() { this.targetId = 'id' + this.siteList[ind - 4].sid }); if (this.state == 0) { this.stateBus = res.data.stateBus var travelsList = this.stateBus.travels this.distance = this.findAllClosest(this.stateBus, this.targetOrder) if (this.distance.length != 0) { var travels = this.distance[0].travels this.travelDistance = travels[0].travelDistance this.travelOrder = travels[0].travelOrder this.travelTime = travels[0].travelTime this.siteList = this.processArrays(this.siteList, this.stateBus, 'order', 'state'); } } } }, async showActive(index, siteName) { this.ind = index this.stationName = siteName var res = await getRealTimeBusLineStation({ lineId: this.lineId, stationName: this.stationName }) if (res.code == 0) { this.siteList = res.data.busInfStop var ind = res.data.targetOrder this.stationName = this.siteList[ind - 1].sn this.state = res.data.state this.busLineBase = res.data.busLineBase this.ind = res.data.targetOrder - 1 this.targetOrder = res.data.targetOrder this.$nextTick(function() { this.targetId = 'id' + this.siteList[ind - 4].sid }); if (this.state == 0) { this.stateBus = res.data.stateBus var travelsList = this.stateBus.travels this.distance = this.findAllClosest(this.stateBus, this.targetOrder) if (this.distance.length != 0) { var travels = this.distance[0].travels this.travelDistance = travels[0].travelDistance this.travelOrder = travels[0].travelOrder this.travelTime = travels[0].travelTime this.isArrive = this.distance[0].state this.siteList = this.processArrays(this.siteList, this.stateBus, 'order', 'state'); } } } }, handleBack() { uni.navigateBack() }, async refresh() { console.log("刷新") var res = await getRealTimeBusLineStation({ lineId: this.lineId, stationName: this.stationName }) if (res.code == 0) { this.siteList = res.data.busInfStop var ind = res.data.targetOrder this.stationName = this.siteList[ind - 1].sn this.state = res.data.state this.busLineBase = res.data.busLineBase this.ind = res.data.targetOrder - 1 this.targetOrder = res.data.targetOrder this.$nextTick(function() { this.targetId = 'id' + this.siteList[ind - 4].sid }); if (this.state == 0) { this.stateBus = res.data.stateBus var travelsList = this.stateBus.travels this.distance = this.findAllClosest(this.stateBus, this .targetOrder) if (this.distance.length != 0) { var travels = this.distance[0].travels this.travelDistance = travels[0].travelDistance this.travelOrder = travels[0].travelOrder this.travelTime = travels[0].travelTime this.isArrive = this.distance[0].state this.siteList = this.processArrays(this.siteList, this .stateBus, 'order', 'state'); } } } }, // 找到距离最近车站的小车 findAllClosest(arr, target) { const validItems = arr.filter(item => item.order < target + 1); if (validItems.length === 0) return []; const distances = validItems.map(item => ({ item, distance: Math.abs(item.order - target) })); if (distances.length != 0) { const minDist = Math.min(...distances.map(d => d.distance)); return distances.filter(d => d.distance === minDist).map(d => d.item); } }, processArrays(arrayA, arrayB, key, valueKey) { // 创建arrayB的快速查找映射(基于指定键) const bMap = new Map(); arrayB.forEach(item => { if (item[key] !== undefined) { bMap.set(item[key], item); } }); // 遍历arrayA并添加属性 return arrayA.map(itemA => { // 浅拷贝避免修改原对象 const newItem = { ...itemA }; // 检查是否存在匹配元素 if (bMap.has(newItem[key])) { const matchedB = bMap.get(newItem[key]); // newItem.state = 0; newItem.isHaveBus = 0; // 添加arrayB中的属性值(如果存在) if (matchedB[valueKey] !== undefined) { newItem[valueKey] = matchedB[valueKey]; } } return newItem; }); } }, onPullDownRefresh() { console.log("下拉") this.refresh() setTimeout(() => { //结束下拉刷新 uni.stopPullDownRefresh(); }, 1000); }, onUnload() { console.log('页面即将卸载'); if (this.timer) { clearInterval(this.timer); this.timer = null; } }, } </script> <style> page { background-color: #f5f5f5; } </style> <style scoped> .bg-image { position: absolute; } .custom-navbar { position: absolute; top: 0; left: 0; width: 100%; z-index: 1000; /* 自定义背景色 */ /* 自定义阴影 */ } .navbar-content { height: 44px; /* 标准导航栏高度 */ display: flex; align-items: center; padding: 0 15px; } .title { flex: 1; text-align: center; font-size: 17px; font-weight: bold; color: #333; /* 标题颜色 */ } .back-btn { width: 48rpx; height: 48rpx; } .right-area { width: 40px; text-align: right; } .info_wapper { height: 156px; /* background: #36A3F9; */ /* background: url(@/static/home/bus/detail-bg.png) left no-repeat; */ padding: 15px; color: black; position: absolute; z-index: 9; width: 100%; } .line_info { position: relative; /* display: flex; */ } .site { font-size: 32rpx; color: black; margin-bottom: 10px; text-align: left; display: flex; margin-left: 30px; } .from { /* padding-left: 52rpx; */ color: black; } .arrow { background: url(@/static/home/bus/single.png) left no-repeat; background-size: 14px 5px; margin-left: 10px; color: black; } .to { padding-left: 26px; color: black; } .time_s { background: url(@/static/home/bus/start.png) left no-repeat; background-size: 18px 18px; justify-self: baseline; } .time_e { background: url(@/static/home/bus/end.png) left no-repeat; background-size: 18px 18px; margin-left: 15px; } .price { margin-left: 15px; } .tit { color: #fff; font-size: 26rpx; /* padding-left: 52rpx; */ margin-bottom: 10px; height: 21px; color: #333; display: flex; align-items: baseline; margin-left: 30px; } .distance-class { border-radius: 15px; /* box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1); */ background: url(@/static/home/bus/distance-bg.png) center no-repeat; background-size: cover; height: 150px; position: absolute; z-index: 9999; left: 20px; right: 20px; } .distance { color: #B7BABB; line-height: 20px; text-align: center; display: flex; flex-direction: column; align-items: center; position: relative; z-index: 9999; /* align-items: baseline; */ } .time { color: aquamarine; } .bus-arr { color: rgb(255, 164, 17); font-weight: bold; margin-top: 10px; } .bus-coming { color: rgb(55, 170, 126); font-weight: bold; line-height: 100rpx; } .station { margin-left: 20px; color: #353738; padding: 10px; } .station-title { display: flex; align-items: baseline; } .station-name { margin-right: 15px; } .station-content { padding-top: 10px; /* padding-left: 20rpx; */ position: relative; /* padding-right: 50rpx; */ /* 确保文字在下划线上方 */ z-index: 2; justify-content: center; display: flex; align-items: center; /* 文字在上层 */ } .underline { position: absolute; bottom: 2px; height: 3px; background: linear-gradient(to right, rgb(110, 207, 169), rgb(110, 224, 176)); /* 下划线颜色 */ border-radius: 3px; z-index: -1; width: 80%; align-items: center; justify-content: center; display: flex; /* margin-left: 30rpx; */ /* width: 60rpx; */ /* 下划线在文字下方 */ } .active { color: black; /* 激活状态文字颜色 */ font-weight: bold; /* font-size: 36rpx; */ } .time { float: left; width: 40%; font-size: 26rpx; /* height: 130rpx; */ line-height: 30px; } .time { font-size: 36rpx; color: #353738; } .time-text { margin-left: 25px; } .time_title { color: rgb(14, 170, 112); font-weight: bold; } .arr { float: left; /* width: 20%; */ font-size: 30rpx; color: rgb(14, 170, 112); } .site_list { /* margin: 15px; */ /* box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1); */ /* background-color: white; */ border-radius: 15px; margin-top: 140px; padding-left: 15px; padding-right: 15px; padding-top: 15px; padding-bottom: 10px; background: url(@/static/home/bus/site-bg.png) center no-repeat; position: absolute; z-index: 9999; left: 20px; right: 20px; background-size: cover; /* 让背景图片覆盖整个视口 */ } .site_list .wapper_sitelist { width: 100%; overflow-x: scroll; position: relative; -webkit-overflow-scrolling: touch; display: flex; /* margin-bottom: 200px; */ } .bus_road { background: url(@/static/home/bus/bus.png) no-repeat center; background-size: cover; width: 28px; height: 14px; position: absolute; z-index: 1; margin-top: 4px; left: 14px; /* margin-top: -40px; */ } .road-bus { background: url(@/static/home/bus/bus.png) no-repeat center; background-size: cover; width: 28px; height: 14px; position: absolute; z-index: 1; margin-top: -45px; margin-left: 10px; /* left: 14px; */ /* margin-top: -40px; */ } .road-bus-coming { background: url(@/static/home/bus/bus.png) no-repeat center; background-size: cover; width: 28px; height: 14px; position: absolute; z-index: 1; margin-top: -45px; margin-left: -40px; } .road { display: flex; } .wapper_sitelist::-webkit-scrollbar { display: none; } /deep/::-webkit-scrollbar { display: none; width: 0; height: 0; } .part { padding: 10px; display: flex; flex-direction: row; text-align: center; justify-content: center; padding-top: 20px; } .site_list .dot_r { margin-top: 10px; position: absolute; top: 23px; height: 3px; background: linear-gradient(to right, rgb(110, 207, 169), rgb(110, 224, 176)); /* 下划线颜色 */ width: 46px; border-radius: 3rpx; z-index: 1; margin-left: 4px; align-items: center; justify-content: center; display: flex; } .site_list .name { margin-top: 28px; display: flex; flex-direction: column; align-items: center; } .site_list .text { display: flex; text-align: center; margin-left: 5px; flex-direction: column; } .site_list .text_show { display: flex; text-align: center; margin-left: 5px; color: rgb(14, 170, 112); font-weight: bold; } .site_list .dot_show { background: url(@/static/home/bus/position_red.png) no-repeat center; background-size: 14px 18px; } .site_list .dot_d_show { border-radius: 50%; width: 16px; height: 16px; background-color: rgba(110, 207, 169, 0.5); position: absolute; z-index: 9999; top: 26px; margin-left: 8px; display: flex; align-items: center; justify-content: center; } .road-class { display: flex; } .dot_p { border-radius: 50%; width: 8px; height: 8px; background-color: rgb(110, 207, 169); position: absolute; z-index: 9999; top: 30px; display: flex; align-items: center; justify-content: center; /* margin-right: 2px; */ /* margin-left: 10rpx; */ } .dot_p_show { border-radius: 50%; width: 8px; height: 8px; background-color: rgb(14, 170, 112); position: absolute; z-index: 9999; top: 30px; display: flex; align-items: center; justify-content: center; } .text-dir { margin-top: 5px; writing-mode: vertical-rl; letter-spacing: 1px; } .none { color: #848484; font-size: 16px; margin-top: 15px; } .map { /* margin-top: 450px; */ position: absolute; z-index: 999; margin-top: 480px; /* height: 300px; */ } .web-class { /* top: 800px; */ /* height: 300px; */ /* position: absolute; z-index: 99999; */ width: 100%; margin-top: 500px; } .page-body { margin-top: 250px; padding-bottom: 20px; } </style>
最新发布
07-08
<think>我们有一个需求:将webview嵌入页面内,并且可以跟随页面移动。当前代码中已经有一个web-view组件,但是它的位置是固定的(通过webviewStyles设置top:580px等)。我们需要让它能够跟随页面滚动。 在uni-app中,web-view组件是一个原生组件,它的层级最高,不能像普通view一样通过滚动控制位置。因此,我们需要改变思路。 解决方案: 1. 使用scroll-view包裹整个页面内容(包括web-view),这样web-view就可以随着页面滚动。但是注意,web-view是原生组件,在scroll-view中可能会有问题(例如滚动冲突、位置不正确等)。 2. 另一种方法是使用普通view来模拟滚动,但同样因为web-view是原生组件,可能会超出scroll-view的范围,导致无法滚动。 考虑到原生组件的限制,我们可能需要重新设计页面结构,将web-view放在一个可以滚动的容器中,并且使用position:relative,而不是fixed或absolute。 但是,在uni-app中,web-view组件默认是全屏的,即使我们设置样式为relative,它可能仍然会覆盖整个页面。因此,我们需要使用另一个方法:将web-view放在一个可滚动的页面中,并设置其高度,然后通过页面滚动来实现。 然而,根据uni-app官方文档,web-view组件默认是全屏的,不能嵌入在页面中。但我们可以通过设置web-view的样式(如position、top、left等)来调整位置,但注意在iOS上可能会有问题。 在H5端,我们可以使用iframe,但在App端,我们使用的是原生webview,所以需要特殊处理。 实际上,在App端,我们可以使用subNvue来嵌入webview,但这里我们使用的是web-view组件。 根据当前代码,我们尝试将web-view放在一个相对定位的容器中,并设置其高度,然后让整个页面可以滚动。 修改步骤: 1. 将整个页面内容(包括web-view)包裹在一个大的view中,并设置样式为可滚动(例如使用overflow-y: scroll; height: 100vh; 注意:在移动端使用100vh可能有兼容性问题,可以使用100%代替,但需要确保父元素有明确高度)。 2. 调整web-view的样式,将其position设置为relative,并调整top、left等属性,使其在文档流中。 但是,由于web-view在App端是原生组件,在滚动时可能会脱离页面滚动,所以我们需要使用nvue页面或者subNvue来优化。但这里我们暂时不改变页面类型(vue页面)。 另一种思路:使用uni-app的cover-view?但cover-view只能覆盖在原生组件上,不能包裹原生组件。 因此,我们可能需要接受web-view不能随页面滚动的事实,或者改变设计:将web-view放在一个固定位置,不随页面滚动,或者将其他内容放在web-view上方(通过绝对定位)并滚动,但这样web-view不会滚动。 根据需求,我们需要web-view跟随页面移动,即当页面滚动时,web-view也要滚动。这在App端很难实现,因为原生组件层级最高,且不能嵌入滚动。 所以,我们可能需要改变方案:将地图部分(目前是web-view)放在一个固定高度的滚动区域内,然后整个页面可以滚动。但web-view在滚动时会出现问题。 考虑到这些,我们或许可以放弃使用web-view,而使用地图组件(如uni-app的map组件)来展示地图。这样,map组件虽然是原生组件,但uni-app已经处理了滚动问题,它可以在scroll-view中滚动(但注意,在iOS上,map组件在滚动时可能不会实时跟随,而是滚动结束后重新渲染位置)。 当前代码中已经注释了一个map组件,我们可以考虑使用uni-app的map组件代替web-view。 因此,我建议: 1. 使用uni-app的map组件来展示地图,这样它就可以在页面中正常滚动。 2. 将原来web-view的部分替换为map组件。 修改代码: 1. 将原来注释的map组件部分取消注释,并调整样式。 2. 删除web-view组件。 但是,原web-view中加载的是一个html文件(src: './static/web/map.html'),如果使用map组件,我们需要将地图功能用map组件重新实现。 如果无法重新实现,则只能继续使用web-view,但无法做到随页面滚动。 根据需求,我们可能需要在App端使用web-view展示地图,并且希望它滚动。目前来看,在App端,web-view组件无法在滚动视图中正常使用。 因此,我们可能需要将web-view放在一个固定位置,然后通过页面其他部分的滚动来模拟整体滚动,但这样web-view会固定在屏幕上,不会滚动。 或者,我们可以将整个页面放在web-view中,通过网页来实现所有功能,但这样就不能使用uni-app的组件API了。 考虑到时间,我们选择使用uni-app的map组件来替换web-view。 修改步骤: 1. 删除web-view组件。 2. 取消注释map组件,并调整样式,将其放在合适的位置(比如在站点列表下面)。 3. 设置map组件的样式(宽度100%,高度300px),并绑定经纬度标记点。 由于原web-view中加载的map.html我们不知道具体内容,所以这里假设我们只需要展示一个地图,并标记一些点。 我们使用uni-app的map组件,并设置latitudelongitude为中心点,标记点通过covers数组设置。 修改后的代码: 在template中,删除web-view部分,并取消注释map部分,将map组件放在站点列表下面。 同时,调整map组件的样式,使其在页面中正常显示。 另外,注意原web-view的样式是设置了一个top:580px,现在我们要让map组件在站点列表下面自然排列,所以不需要设置top。 具体修改如下: 1. 在站点列表(site_list)的view后面,添加map组件,并设置样式(比如高度300px)。 2. 绑定经纬度数据,这里我们使用data中的latitudelongitude,以及covers数组。 注意:原代码中有一个iframe,我们也一并删除。 修改后的template部分(只修改相关部分):</think>为了实现在页面内嵌入WebView并使其跟随页面移动,我将重构代码结构,使用`position: sticky`实现粘性定位,并调整布局结构。以下是修改后的解决方案: ```html <template> <view class="container"> <!-- 顶部背景导航栏 --> <!-- ... 保留原有顶部结构 ... --> <!-- 内容区域 --> <view class="content"> <!-- 线路信息 --> <!-- ... 保留原有线路信息 ... --> <!-- 站点列表 --> <view class="site_list"> <!-- ... 保留原有站点列表结构 ... --> </view> <!-- 粘性定位的地图容器 --> <view class="sticky-map-container"> <!-- 使用sticky定位的WebView包装器 --> <view class="webview-wrapper"> <web-view ref="webview" :src="src" :webview-styles="webviewStyles" :fullscreen="false" ></web-view> </view> </view> </view> </view> </template> <script> // ... 保留原有脚本内容 ... export default { data() { return { // ... 其他数据 ... webviewStyles: { position: 'relative', height: '300px', // 设置固定高度 width: '100%', zIndex: 10 }, } }, // ... 保留原有方法 ... } </script> <style scoped> /* 新增粘性容器样式 */ .container { display: flex; flex-direction: column; height: 100vh; overflow: hidden; } .content { flex: 1; overflow-y: auto; position: relative; } .sticky-map-container { position: sticky; bottom: 0; z-index: 100; background: white; padding: 10px; box-shadow: 0 -2px 10px rgba(0,0,0,0.1); } .webview-wrapper { border-radius: 12px; overflow: hidden; box-shadow: 0 2px 12px rgba(0,0,0,0.1); height: 300px; /* 与webviewStyles高度一致 */ } /* 调整站点列表高度 */ .site_list { min-height: 200px; /* 确保有足够空间滚动 */ margin-bottom: 20px; } /* 保留原有其他样式... */ </style> ``` ### 关键修改说明: 1. **布局结构调整**: - 使用flex布局创建容器,确保内容区域可滚动 - 添加`.sticky-map-container`作为粘性定位容器 - 站点列表添加`margin-bottom`避免被地图覆盖 2. **粘性定位实现**: ```css .sticky-map-container { position: sticky; bottom: 0; /* 固定在底部 */ z-index: 100; /* 确保在内容上方 */ } ``` 这会使WebView在滚动时保持在视窗底部,跟随页面移动 3. **WebView样式优化**: - 设置固定高度(300px)避免内容跳动 - 添加圆角阴影提升视觉效果 - 移除绝对定位相关属性 4. **滚动体验优化**: - 主内容区域设置`overflow-y: auto`启用滚动 - 确保站点列表有足够高度(min-height)触发滚动 ### 注意事项: 1. 在iOS设备上可能需要添加`-webkit-sticky`前缀: ```css .sticky-map-container { position: -webkit-sticky; position: sticky; } ``` 2. 如果地图交互需要更多空间,可通过按钮切换全屏模式: ```html <view class="map-toggle" @click="toggleMapSize"> <image :src="isMapExpanded ? '/static/minimize.png' : '/static/expand.png'" /> </view> ``` 在data中添加`isMapExpanded`状态,动态修改webview高度 3. 确保WebView内容适合嵌入式显示,可能需要调整地图HTML的UI
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值