固定比例
padding实现
<template> <view class="outter" :style="outerRatioStyle"> <view class="inner"> <slot></slot> </view> </view></template><script>export default { name: 'aspect-ratio', data () { return {} }, props: { ratio: { type: Number } }, computed: { outerRatioStyle () { let ratio = this.ratio if (ratio == null || Number.isNaN(ratio)) { ratio = 100 } if (typeof ratio !== 'string' || !ratio.endsWith('%')) { ratio = ratio + '%' } return `padding-top: ${ratio}` } }}</script><style lang="scss" scoped>.outter { position: relative; width: 100%; height: 0; .inner { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }}</style>
aspect-ratio实现
/* 高度随动 */.box1 { width: 100%; height: auto; aspect-ratio: 16/9;}/* 宽度随动 */.box2 { height: 100%; width: auto; aspect-ratio: 16/9;}
代码预览+复制
子组件
<template> <div class="pre-code-wrap hljs"> <div class="pre-tips"> <span class="lang">{{props.lang}}</span> <span class="btn" data-clipboard-action="copy" :data-clipboard-target="'#highlight-'+idName" @click="copyCode">{{props.btn}}</span> </div> <pre v-highlight :class="props.lang" :id="'highlight-'+idName"><code><slot></slot></code></pre> </div> <teleport to="body"> <div class="is-copy sus" v-if="success">{{props.tips[0]}}</div> <div class="is-copy err" v-if="error">{{props.tips[1]}}</div> </teleport></template><script>import hljs from "highlight.js"export default { // 自定义指令 directives: { highlight: { deep: true, beforeMount (el, binding) { let targets = el.querySelectorAll("code") targets.forEach((target) => { if (typeof binding.value === "string") { target.textContent = binding.value } hljs.highlightElement(target) }) }, updated (el, binding) { let targets = el.querySelectorAll("code") targets.forEach((target) => { if (typeof binding.value === "string") { target.textContent = binding.value hljs.highlightElement(target) } }) } } }}</script><script setup>import Clipboard from 'clipboard';import { defineProps, defineEmits, ref } from 'vue';// 定义参数const props = defineProps({ lang: { type: String, default: 'js' }, btn: { type: String, default: '复制代码' }, tips: { type: Array, default: () => ['代码复制成功', '该浏览器不支持自动复制'], validator: (value) => { return value.length >= 2 } }})// 传递事件声明const emits = defineEmits(['on-copy'])// 数据const success = ref(false)const error = ref(false)// 不重复idconst idName = Math.random().toString(36).slice(-8)// 事件const copyCode = () => { const clipboard = new Clipboard('.btn') clipboard.on('success', (e) => { success.value = true setTimeout(() => { success.value = false }, 3000); clipboard.destroy(); e.clearSelection(); emits('on-copy', 'success') }) clipboard.on('error', (e) => { error.value = true setTimeout(() => { error.value = false }, 3000); clipboard.destroy() e.clearSelection(); emits('on-copy', 'error') })}</script><style lang="scss" scoped>.pre-code-wrap { position: relative; width: 100%;}.pre-tips { font-size: 12px; line-height: 1; position: absolute; top: 4px; right: 4px; user-select: none;}.lang { margin-right: 10px; opacity: 0.4;}.btn { cursor: pointer; opacity: 0.4; &:hover { opacity: 1; }}.is-copy { line-height: 1.2; line-height: 1.2; position: fixed; z-index: 1000; top: 20px; right: 20px; padding: 10px 20px;}.sus { color: #67c23a; border: 1px solid #67c23a; background-color: #f0f9eb;}.err { color: #f56c6c; border: 1px solid #f56c6c; background-color: #fef0f0;}</style>
父组件
<template>
<pre-code lang="js">{{js}}</pre-code>
<br />
<pre-code lang="scss" theme="dark" :tips="['成功','失败']" btn="复制">{{scss}}</pre-code>
</template>
<script setup>
import { ref } from '@vue/reactivity';
import preCode from './../components/pre.vue';
const scss = ref(
`.pre-code-wrap {
position: relative;
.copy-btn {
font-size: 12px;
line-height: 1;
position: absolute;
top: 4px;
right: 4px;
cursor: pointer;
}
}
.is-copy-tips {
line-height: 1.2;
position: fixed;
z-index: 1000;
top: 20px;
right: 20px;
padding: 10px 20px;
color: #67c23a;
border: 1px solid #67c23a;
background-color: #f0f9eb;
}`)
const js = ref(
`import { createApp } from "vue"
import App from "./App.vue"
import router from "./router"
import store from "./store"
import ElementPlus from "element-plus"
import zhCn from "element-plus/es/locale/lang/zh-cn"
import "element-plus/dist/index.css"
import "./assets/css/reset.css"
const app = createApp(App)
app.use(store)
app.use(router)
app.use(ElementPlus, { locale: zhCn })
app.mount("#app")
`)
</script>
<style src="highlight.js/styles/atom-one-light.css"></style>
锚点-滚动定位
<template>
<div class="page">
<ul class="title">
<li :class="{'active':active==index}" v-for="(li,index) in titleData" :key="li" @click="goAnchor(li,index)">{{li}}</li>
</ul>
<ul class="item-wrap" @scroll="changeScroll">
<li class="item" v-for="li in titleData" :key="li" :id="li">
<h2>{{li}}</h2>
<p v-for="i in 100" :key="i">{{li}}-{{i}}</p>
</li>
</ul>
</div>
</template>
<script setup>
import { getCurrentInstance, ref, onMounted, reactive } from 'vue';
const { proxy } = getCurrentInstance()
let active = ref(0)
const titleData = reactive(['Anchor1', 'Anchor2', 'Anchor3', 'Anchor4'])
// 点击锚点
const goAnchor = (name, index) => {
document.querySelector('#' + name).scrollIntoView({ behavior: "smooth" })
}
// 监听滚动事件
const changeScroll = () => {
const boxElList = document.querySelectorAll('.item');
boxElList.forEach((el) => {
isView.observe(el);
})
}
// 监听是否在可视区域
const isView = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.intersectionRatio > 0) {
let key = titleData.findIndex((e) => e == entry.target.id)
active.value = key
}
})
})
</script>
<style lang='scss' scoped>
.page {
position: relative;
}
.title {
font-size: 20px;
position: sticky;
top: 0;
left: 0;
display: flex;
align-items: center;
flex-direction: row;
width: 100%;
border-bottom: 1px solid #000;
background-color: #fff;
li {
padding: 0 20px;
cursor: pointer;
}
.active {
color: #fff;
background-color: #fc0;
}
}
.item-wrap {
overflow-y: auto;
height: 500px;
.item {
padding-top: 10px;
}
}
</style>