ABOUT ME

-

  • [ Vue ] Splitpanes 화면 분할
    Front/Vue 2025. 6. 19. 23:33
    반응형

    사이드바를 만들면서 사용자가 드래그래서 크기를 자유롭게 조절할 수 있도록 기능을 구현하고자하여 splitpanes를 사용


    Installment

     npm install splitpanes@next

     

    element-plus를 사용하면서 element-plus에도 split이 있긴하지만 아직 베타버전이기에 splitpanes를 사용

     


    Split Component

    split 컴포넌트를 만들어 코드 관리를 좀 더 편하게 관리

    splitpanes를 사용할때는 splitpanes와 pane 그리고 css를 import하여 사용해야한다.

     

    측면 사이드바를 버튼을 눌러 닫고 열고가 가능하게 할려고해서 

    pane에 size props를 전달해 외부에서도 사이즈를 조절할 수 있도록 설정

     

    버튼을 누르면 측면 사이드바의 닫힘 여부를 store를 사용하여 관리하고 있기에

    여기서 반응형으로 store의 값이 바뀌면 pane의 사이즈값도 변경되도록 설정

     

    더보기

    이때 다른 pane에는 전체 크기인 100%에서 변경된 size만큼 빼주어야한다.

    안하면 left는 70그대로 right는 0으로 되면서 split에서 left공간이 빈공간으로만 차지해진다.

     

    추가로 splitpanes를 사용할때는 size에는 %단위로만 들어가므로

    slot에 들어갈 element들의 split에서로 처리해야하므로 width값은 설정해주지 않는다.

    split 컴포넌트에 appStore를 불러와서 하는 방식 보다는 defineModel로 외부와 값과 반응형을 연결하여 가져오는게 더 재사용성을 높일 수 있지만, 지금은 사이드바 관련되서만 사용할 것 같아 그냥 split 컴포넌트에서 처리했다.

    <script setup lang="ts">
    import { Splitpanes, Pane } from 'splitpanes'
    import 'splitpanes/dist/splitpanes.css'
    import {computed} from 'vue'
    import {useAppStore} from "@/store/module/appStore.ts";
    
    const appStore = useAppStore()
    
    const rightSize = computed(() => {
      return appStore.appAsideBar ? 30 : 0
    })
    
    </script>
    
    <template>
      <Splitpanes>
        <Pane :size="100-rightSize">
          <slot name="left"/>
        </Pane>
        <Pane :size="rightSize">
          <slot name="right" />
        </Pane>
      </Splitpanes>
    </template>
    
    <style scoped lang="scss">
    
    </style>

     

    이후 css를 적당히 수정하여 원하는 스타일로 적용

     

     

    우선 사이드바가 닫히면 splitpanes의 화면 드래그 부분인 splitter를 지워주며,

     

    기존에는 1px인 부분이 너무 얇은 것 같아 5px로 증가

    이후 닫힐때에는 기존 splitpanes의 애니메이션이 0.2s로 설정되어있는데 이를 닫힐때와 열릴때 속도를 조절하기 위해 

    delay 클래스를 주며 .5s로 설정 현재 프로젝트의 모든 transition을 .5s로 설정했기에 맞춰주었다.

    splitpnaes_pane에 transition을 바꿔버리면 화면의 split을 할때도 width값이 변경되는 것이므로 마우스로 pane크기를 변경하는 순간에도 delay가 생기게 된다. 따라서 닫힐때 열릴때에만 애니메이션을 주어야 한다. 

    .splitpanes--vertical {
      &.hide {
        .splitpanes__splitter {
          display: none;
        }
      }
      .splitpanes__splitter {
        min-width: 5px !important;
        background-color: red;
      }
    
      .splitpanes__pane {
        &.delay {
          transition: width .5s !important;
        }
      }
    }

     

    따라서 사이드바의 값이 바뀌면 delayTrans 값을 true로 하고 setTimeout으로 false처리하여 애니메이션 시간인 .5s 후에 다시 delay클래스를 없애 기존 splitpanes의 transition인 .2s가 적용되도록 한다.

     

    splitpanes에는 hide 클래스를 주어 사이드바가 닫히면 splitter의 display:none을 주어 사라지게 한다.

    <script setup lang="ts">
    import { Splitpanes, Pane } from 'splitpanes'
    import 'splitpanes/dist/splitpanes.css'
    import {computed, ref, watch} from 'vue'
    import {useAppStore} from "@/store/module/appStore.ts";
    
    const appStore = useAppStore()
    
    const rightSize = computed(() => {
      return appStore.appAsideBar ? 30 : 0
    })
    
    const delayTrans = ref(false)
    
    watch(() => appStore.appAsideBar, () => {
        delayTrans.value = true
        setTimeout(() => {
          delayTrans.value = false
        }, 500)
    })
    
    </script>
    
    <template>
      <Splitpanes :class="{hide: !appStore.appAsideBar}">
        <Pane :size="100-rightSize" :class="{delay: delayTrans}">
          <slot name="left"/>
        </Pane>
        <Pane :size="rightSize" :class="{delay: delayTrans}">
          <slot name="right" />
        </Pane>
      </Splitpanes>
    </template>
    
    <style scoped lang="scss">
    
    </style>

    Useage

    이제 실제 layout 컴포넌트에서 main부분과 aside부분을 split에 적용

    <Split>
            <template #left>
              <div class="layout-main">
                <app-header/>
                <app-main/>
              </div>
            </template>
            <template #right class="layout-aside">
              <div class="layout-aside">
                <app-aside/>
              </div>
            </template>
          </Split>

     

    그럼 화면 분할 드래그와 아이콘을 누르면 닫고 열기도 자연스럽게 적용된다.

    splitter에는 테스트할때는 색을 넣어 잘 보이도록 처리


    Additional work

    이제 splitter를 건드려서 닫을 수도 있게 추가 작업을 위해
    현재 pane2의 size를 가져와야한다. size는 반응형이 아니므로 size가 변해도 rightSize에는 변경된 size가 안나오므로, 직접 계산을 해야한다

     

    const sizes = ref([70, 30])

    <Splitpanes v-model:split-sizes="sizes"> 로 서로 바인딩을 시킬 수 있다고는 하는데 잘 안돼서 다른 방법으로 진행

     

    observer를 설정하여 pane2가 변경될때마다 함수를 실행시켜 pane2의 요소에서 width값을 가져와서 체크한 후

    width값이 0이 되면 사이드바는 닫힌걸로 판단하여 asidebar를 false로 업데이트 시켜 splitter를 지워주는 방식으로 진행

     

    <script setup lang="ts">
    const pane2Ref = ref()
    let resizeObserver: ResizeObserver
    
    function updateCSSVar() {
      if (pane2Ref.value) {
        const size = pane2Ref.value.$el.clientWidth
        if(size == 0) {
          appStore.setAsideBar(APP_ASIDE_CLOSE)
        }
      }
    }
    onMounted(() => {
      resizeObserver = new ResizeObserver(() => {
        updateCSSVar()
      })
    
      // 실제 DOM 노드를 observe
      if (pane2Ref.value?.$el) {
        resizeObserver.observe(pane2Ref.value.$el)
      }
    })
    
    onBeforeUnmount(() => {
      if (resizeObserver && pane2Ref.value?.$el) {
        resizeObserver.unobserve(pane2Ref.value.$el)
      }
    })
    </script>
    
    <template>
      <Splitpanes :class="{hide: !appStore.appAsideBar}">
        <Pane :size="100-rightSize" :class="{delay: delayTrans}">
          <slot name="left"/>
        </Pane>
        <Pane ref="pane2Ref" :size="rightSize" :class="{delay: delayTrans}">
          <slot name="right" />
        </Pane>
      </Splitpanes>
    </template>
    728x90
    반응형