固定比例

padding实现

  1. <template>
  2. <view class="outter" :style="outerRatioStyle">
  3. <view class="inner">
  4. <slot></slot>
  5. </view>
  6. </view>
  7. </template>
  8. <script>
  9. export default {
  10. name: 'aspect-ratio',
  11. data () {
  12. return {}
  13. },
  14. props: {
  15. ratio: {
  16. type: Number
  17. }
  18. },
  19. computed: {
  20. outerRatioStyle () {
  21. let ratio = this.ratio
  22. if (ratio == null || Number.isNaN(ratio)) {
  23. ratio = 100
  24. }
  25. if (typeof ratio !== 'string' || !ratio.endsWith('%')) {
  26. ratio = ratio + '%'
  27. }
  28. return `padding-top: ${ratio}`
  29. }
  30. }
  31. }
  32. </script>
  33. <style lang="scss" scoped>
  34. .outter {
  35. position: relative;
  36. width: 100%;
  37. height: 0;
  38. .inner {
  39. position: absolute;
  40. top: 0;
  41. left: 0;
  42. width: 100%;
  43. height: 100%;
  44. }
  45. }
  46. </style>

aspect-ratio实现

  1. /* 高度随动 */
  2. .box1 {
  3. width: 100%;
  4. height: auto;
  5. aspect-ratio: 16/9;
  6. }
  7. /* 宽度随动 */
  8. .box2 {
  9. height: 100%;
  10. width: auto;
  11. aspect-ratio: 16/9;
  12. }

代码预览+复制

子组件

  1. <template>
  2. <div class="pre-code-wrap hljs">
  3. <div class="pre-tips">
  4. <span class="lang">{{props.lang}}</span>
  5. <span class="btn" data-clipboard-action="copy" :data-clipboard-target="'#highlight-'+idName" @click="copyCode">{{props.btn}}</span>
  6. </div>
  7. <pre v-highlight :class="props.lang" :id="'highlight-'+idName"><code><slot></slot></code></pre>
  8. </div>
  9. <teleport to="body">
  10. <div class="is-copy sus" v-if="success">{{props.tips[0]}}</div>
  11. <div class="is-copy err" v-if="error">{{props.tips[1]}}</div>
  12. </teleport>
  13. </template>
  14. <script>
  15. import hljs from "highlight.js"
  16. export default {
  17. // 自定义指令
  18. directives: {
  19. highlight: {
  20. deep: true,
  21. beforeMount (el, binding) {
  22. let targets = el.querySelectorAll("code")
  23. targets.forEach((target) => {
  24. if (typeof binding.value === "string") {
  25. target.textContent = binding.value
  26. }
  27. hljs.highlightElement(target)
  28. })
  29. },
  30. updated (el, binding) {
  31. let targets = el.querySelectorAll("code")
  32. targets.forEach((target) => {
  33. if (typeof binding.value === "string") {
  34. target.textContent = binding.value
  35. hljs.highlightElement(target)
  36. }
  37. })
  38. }
  39. }
  40. }
  41. }
  42. </script>
  43. <script setup>
  44. import Clipboard from 'clipboard';
  45. import { defineProps, defineEmits, ref } from 'vue';
  46. // 定义参数
  47. const props = defineProps({
  48. lang: {
  49. type: String,
  50. default: 'js'
  51. },
  52. btn: {
  53. type: String,
  54. default: '复制代码'
  55. },
  56. tips: {
  57. type: Array,
  58. default: () => ['代码复制成功', '该浏览器不支持自动复制'],
  59. validator: (value) => {
  60. return value.length >= 2
  61. }
  62. }
  63. })
  64. // 传递事件声明
  65. const emits = defineEmits(['on-copy'])
  66. // 数据
  67. const success = ref(false)
  68. const error = ref(false)
  69. // 不重复id
  70. const idName = Math.random().toString(36).slice(-8)
  71. // 事件
  72. const copyCode = () => {
  73. const clipboard = new Clipboard('.btn')
  74. clipboard.on('success', (e) => {
  75. success.value = true
  76. setTimeout(() => {
  77. success.value = false
  78. }, 3000);
  79. clipboard.destroy();
  80. e.clearSelection();
  81. emits('on-copy', 'success')
  82. })
  83. clipboard.on('error', (e) => {
  84. error.value = true
  85. setTimeout(() => {
  86. error.value = false
  87. }, 3000);
  88. clipboard.destroy()
  89. e.clearSelection();
  90. emits('on-copy', 'error')
  91. })
  92. }
  93. </script>
  94. <style lang="scss" scoped>
  95. .pre-code-wrap {
  96. position: relative;
  97. width: 100%;
  98. }
  99. .pre-tips {
  100. font-size: 12px;
  101. line-height: 1;
  102. position: absolute;
  103. top: 4px;
  104. right: 4px;
  105. user-select: none;
  106. }
  107. .lang {
  108. margin-right: 10px;
  109. opacity: 0.4;
  110. }
  111. .btn {
  112. cursor: pointer;
  113. opacity: 0.4;
  114. &:hover {
  115. opacity: 1;
  116. }
  117. }
  118. .is-copy {
  119. line-height: 1.2;
  120. line-height: 1.2;
  121. position: fixed;
  122. z-index: 1000;
  123. top: 20px;
  124. right: 20px;
  125. padding: 10px 20px;
  126. }
  127. .sus {
  128. color: #67c23a;
  129. border: 1px solid #67c23a;
  130. background-color: #f0f9eb;
  131. }
  132. .err {
  133. color: #f56c6c;
  134. border: 1px solid #f56c6c;
  135. background-color: #fef0f0;
  136. }
  137. </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>