| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238 |
- <template>
- <div class="hamster-ppt-screen">
- <div
- class="slide-list"
- @mousewheel="$event => mousewheelListener($event)"
- v-contextmenu="contextmenus"
- >
- <div
- :class="[
- 'slide-item',
- {
- 'show': index === slideIndex,
- 'prev': index < slideIndex,
- 'next': index > slideIndex,
- }
- ]"
- v-for="(slide, index) in slides"
- :key="slide.id"
- >
- <div
- class="slide-content"
- :style="{
- width: slideWidth + 'px',
- height: slideHeight + 'px',
- }"
- >
- <ScreenSlide
- :scale="scale"
- :slide="slide"
- :animationIndex="animationIndex"
- />
- </div>
- </div>
- </div>
- </div>
- </template>
- <script lang="ts">
- import { computed, defineComponent, onMounted, onUnmounted, Ref, ref } from 'vue'
- import { useStore } from 'vuex'
- import throttle from 'lodash/throttle'
- import { MutationTypes, State } from '@/store'
- import { exitFullscreen, isFullscreen } from '@/utils/fullscreen'
- import { VIEWPORT_ASPECT_RATIO, VIEWPORT_SIZE } from '@/configs/canvas'
- import { KEYS } from '@/configs/hotkey'
- import { ContextmenuItem } from '@/components/Contextmenu/types'
- import ScreenSlide from './ScreenSlide.vue'
- import { Slide } from '@/types/slides'
- export default defineComponent({
- name: 'screen',
- components: {
- ScreenSlide,
- },
- setup() {
- const store = useStore<State>()
- const slides = computed(() => store.state.slides)
- const slideIndex = computed(() => store.state.slideIndex)
- const currentSlide: Ref<Slide> = computed(() => store.getters.currentSlide)
- const slideWidth = ref(0)
- const slideHeight = ref(0)
- const scale = computed(() => slideWidth.value / VIEWPORT_SIZE)
- const setSlideContentSize = () => {
- const winWidth = document.body.clientWidth
- const winHeight = document.body.clientHeight
- let width, height
- if(winHeight / winWidth === VIEWPORT_ASPECT_RATIO) {
- width = winWidth
- height = winHeight
- }
- else if(winHeight / winWidth > VIEWPORT_ASPECT_RATIO) {
- width = winWidth
- height = winWidth * VIEWPORT_ASPECT_RATIO
- }
- else {
- width = winHeight / VIEWPORT_ASPECT_RATIO
- height = winHeight
- }
- slideWidth.value = width
- slideHeight.value = height
- }
- const windowResizeListener = () => {
- setSlideContentSize()
- if(!isFullscreen()) store.commit(MutationTypes.SET_SCREENING, false)
- }
- const animationIndex = ref(0)
- const animations = computed(() => currentSlide.value.animations || [])
- const runAnimation = () => {
- const prefix = 'animate__'
- const animation = animations.value[animationIndex.value]
- animationIndex.value += 1
- const elRef = document.querySelector(`#screen-element-${animation.elId} [class^=base-element-]`)
- if(elRef) {
- const animationName = `${prefix}${animation.type}`
- elRef.classList.add(`${prefix}animated`, animationName)
- const handleAnimationEnd = () => {
- elRef.classList.remove(`${prefix}animated`, animationName)
- }
- elRef.addEventListener('animationend', handleAnimationEnd, { once: true })
- }
- }
- const execPrev = () => {
- if(animations.value.length && animationIndex.value > 0) {
- animationIndex.value -= 1
- }
- else if(slideIndex.value > 0) {
- store.commit(MutationTypes.UPDATE_SLIDE_INDEX, slideIndex.value - 1)
- const lastIndex = animations.value ? animations.value.length : 0
- animationIndex.value = lastIndex
- }
- }
- const execNext = () => {
- if(animations.value.length && animationIndex.value < animations.value.length) {
- runAnimation()
- }
- else if(slideIndex.value < slides.value.length - 1) {
- store.commit(MutationTypes.UPDATE_SLIDE_INDEX, slideIndex.value + 1)
- animationIndex.value = 0
- }
- }
- const keydownListener = (e: KeyboardEvent) => {
- const key = e.key.toUpperCase()
- if(key === KEYS.UP || key === KEYS.LEFT) execPrev()
- else if(key === KEYS.DOWN || key === KEYS.RIGHT) execNext()
- }
- const mousewheelListener = throttle(function(e: WheelEvent) {
- if(e.deltaY < 0) execPrev()
- else if(e.deltaY > 0) execNext()
- }, 500, { leading: true, trailing: false })
- onMounted(() => {
- window.addEventListener('resize', windowResizeListener)
- document.addEventListener('keydown', keydownListener)
- })
- onUnmounted(() => {
- window.removeEventListener('resize', windowResizeListener)
- document.removeEventListener('keydown', keydownListener)
- })
- const turnPrevSlide = () => {
- store.commit(MutationTypes.UPDATE_SLIDE_INDEX, slideIndex.value - 1)
- animationIndex.value = 0
- }
- const turnNextSlide = () => {
- store.commit(MutationTypes.UPDATE_SLIDE_INDEX, slideIndex.value + 1)
- animationIndex.value = 0
- }
- const contextmenus = (): ContextmenuItem[] => {
- return [
- {
- text: '上一页',
- disable: slideIndex.value <= 0,
- handler: () => turnPrevSlide(),
- },
- {
- text: '下一页',
- disable: slideIndex.value >= slides.value.length - 1,
- handler: () => turnNextSlide(),
- },
- { divider: true },
- {
- text: '结束放映',
- subText: 'ESC',
- handler: exitFullscreen,
- },
- ]
- }
- return {
- slides,
- slideIndex,
- slideWidth,
- slideHeight,
- scale,
- mousewheelListener,
- animationIndex,
- contextmenus,
- }
- },
- })
- </script>
- <style lang="scss" scoped>
- .hamster-ppt-screen {
- width: 100%;
- height: 100%;
- position: relative;
- background-color: #111;
- }
- .slide-list {
- background: #1d1d1d;
- position: relative;
- width: 100%;
- height: 100%;
- }
- .slide-item {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- transition-property: transform;
- transition-duration: .4s;
- &.show {
- z-index: 2;
- }
- &.prev {
- transform: translateY(-100%);
- }
- &.next {
- transform: translateY(100%);
- }
- }
- .slide-content {
- background-color: #fff;
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- display: flex;
- justify-content: center;
- align-items: center;
- }
- </style>
|