返回 slidev
SlideContainer.vue
根目录 / packages / client / internals / SlideContainer.vue
1 <script setup lang="ts">
2 import { provideLocal, useElementSize } from '@vueuse/core'
3 import { recomputeAllPoppers } from 'floating-vue'
4 import { computed, onMounted, onUnmounted, ref, watch, watchEffect, watchSyncEffect } from 'vue'
5 import { useNav } from '../composables/useNav'
6 import { injectionSlideElement, injectionSlideScale } from '../constants'
7 import { slideAspect, slideHeight, slideWidth } from '../env'
8 import { isDark } from '../logic/dark'
9 import { snapshotManager } from '../logic/snapshot'
10 import { slideScale } from '../state'
11
12 const props = defineProps({
13 width: {
14 type: Number,
15 },
16 meta: {
17 default: () => ({}) as any,
18 },
19 isMain: {
20 type: Boolean,
21 default: false,
22 },
23 no: {
24 type: Number,
25 required: false,
26 },
27 useSnapshot: {
28 type: Boolean,
29 default: false,
30 },
31 contentStyle: {
32 type: Object,
33 default: () => ({}),
34 },
35 })
36
37 const { isPrintMode } = useNav()
38
39 const container = ref<HTMLDivElement | null>(null)
40 const containerSize = useElementSize(container)
41 const slideElement = ref<HTMLElement | null>(null)
42
43 const width = computed(() => props.width ?? containerSize.width.value)
44 const height = computed(() => props.width ? props.width / slideAspect.value : containerSize.height.value)
45
46 const scale = computed(() => {
47 if (slideScale.value && !isPrintMode.value)
48 return +slideScale.value
49 return Math.min(width.value / slideWidth.value, height.value / slideHeight.value)
50 })
51
52 const contentStyle = computed(() => ({
53 ...props.contentStyle,
54 'height': `${slideHeight.value}px`,
55 'width': `${slideWidth.value}px`,
56 '--slidev-slide-scale': scale.value,
57 }))
58
59 const containerStyle = computed(() => props.width
60 ? {
61 width: `${props.width}px`,
62 height: `${props.width / slideAspect.value}px`,
63 }
64 : {},
65 )
66
67 if (props.isMain) {
68 const rootStyle = document.documentElement.style
69 watchEffect(() => rootStyle.setProperty('--slidev-slide-scale', scale.value.toString()))
70 onUnmounted(() => rootStyle.removeProperty('--slidev-slide-scale'))
71 }
72
73 provideLocal(injectionSlideScale, scale)
74 provideLocal(injectionSlideElement, slideElement)
75
76 watchSyncEffect(() => {
77 if (props.isMain) {
78 mainSlideElement.value = slideElement.value
79 }
80 })
81
82 onMounted(() => {
83 watch(() => props.isMain && scale.value, () => {
84 recomputeAllPoppers()
85 })
86 })
87
88 const snapshot = computed(() => {
89 if (props.no == null || !props.useSnapshot)
90 return undefined
91 return snapshotManager.getSnapshot(props.no, isDark.value)
92 })
93 </script>
94
95 <script lang="ts">
96 export const mainSlideElement = ref<HTMLElement | null>(null)
97 </script>
98
99 <template>
100 <div
101 v-if="!snapshot"
102 :id="isMain ? 'slide-container' : undefined"
103 ref="container"
104 class="slidev-slide-container"
105 :style="containerStyle"
106 >
107 <div
108 :id="isMain ? 'slide-content' : undefined"
109 ref="slideElement"
110 class="slidev-slide-content"
111 :style="contentStyle"
112 >
113 <slot />
114 </div>
115 <slot name="controls" />
116 </div>
117 <!-- Image Snapshot -->
118 <div v-else class="slidev-slide-container w-full h-full relative">
119 <img
120 :src="snapshot"
121 class="w-full h-full object-cover"
122 :style="containerStyle"
123 >
124 <div absolute bottom-1 right-1 p0.5 text-cyan:75 bg-cyan:10 rounded title="Snapshot">
125 <div class="i-carbon-camera" />
126 </div>
127 </div>
128 </template>
129
130 <style scoped lang="postcss">
131 .slidev-slide-container {
132 @apply relative w-full h-full overflow-hidden;
133 }
134
135 .slidev-slide-content {
136 --slidev-slide-container-scale: var(--slidev-slide-scale);
137 transform: translate(-50%, -50%) scale(var(--slidev-slide-scale));
138 @apply absolute left-1/2 top-1/2 overflow-hidden bg-main;
139 }
140 </style>
141
141 lines Plain Text