Vue에서 Tinder와 유사한 슬라이딩 사용자 인터페이스 만들기

너는 이런 우클릭 좌클릭의 벨벳 같은 사용자 체험이 어떻게 이루어졌는지 생각해 본 적이 있니?네, 며칠 전에요.나는 백그라운드 배경에서 더 많이 왔다. 익숙하지 않은 내 머릿속에서 나는 이런 일이 정말 신기하다고 생각한다.
나 같은 평범한 개발자에게 이런 멋진 것을 구축하는 것은 얼마나 어려운지 궁금하다.

정찰


정보를 수집하는 것은 줄곧 내가 새로운 프로젝트에 종사하는 첫걸음이었다.나는 어떤 코드도 시도하지 않는다. 나는 먼저 구글로 검색할 것이다.내 말은 나보다 똑똑한 사람이 이전에 이 점을 생각해 본 적이 있다는 것이다.
의심할 여지없이'vue swipeable 카드'를 검색한 후 구글이 나에게 준 첫 번째 일은 (나는 운이 좋다)였다.
이것은 article from css-tricksbyMateusz Rybczonek의 사용interact.js 구축에 관한 글입니다.
이 글은 나보다 훨씬 좋은 swipeable 구성 요소를 구축하는 방법을 설명할 것이다.더 중요한 것은 그가 기능을 추출하여 vue2-interact의 신분으로 npm(예, 개원!)에 발표하는 것이다.
이 글은 모든 것이 어떻게 작동하는지 확실히 설명했지만, 본질적으로는 우리의 샘플 코드일 뿐이다.우리가 필요로 하는 것은 추출된 기능 자체를 실제로 사용하는 것이다.이것이 바로 Vue2InteractDraggable이 좋은 이유입니다. 모든 힘든 일은 이미 우리를 위해 완성되었습니다. 이것은 단지 우리가 자신의 프로젝트에서 그것을 어떻게 사용하는가의 문제입니다.

실험


이 점에서 내가 해야 할 일은 그것을 가지고 노는 것이다.이 숫자docs는 매우 명확하다.가장 간단한 코드부터 시작하겠습니다.
<template>
  <section class="container">
    <div class="fixed-center">
      <Vue2InteractDraggable
        :interact-out-of-sight-x-coordinate="500"
        :interact-max-rotation="15"
        :interact-x-threshold="200"
        :interact-y-threshold="200"
        class="rounded-borders shadow-10 card">
        <div class="card__main">    
        </div>
      </Vue2InteractDraggable>
    </div>
  </section>
</template>

<script>
import { Vue2InteractDraggable } from 'vue2-interact'

export default {
  name: 'SwipeableCards',
  components: { Vue2InteractDraggable }
}
</script>
특별한 것은 없습니다. 이것은 단지 중앙 화면에 분홍색 상자가 표시되어 있기 때문에, 나는 그것을 끌 수 있습니다.

쿨, 쿨, 쿨, 쿨.모든 것이 정상이다.이제 우리는 이 점을 검증했으니 내가 하고 싶은 다른 일을 생각해 볼 때가 되었다.
내가 원하는 그런 사용자의 상호작용을 보여주기 위해 나는 일을 다음과 같은 요구로 귀결시켰다.
  • 카드가 시선을 끌지 않고 숨겨졌는지 검사합니다.
  • 드래그할 수 있는 카드를 겹쳐 놓습니다.
  • 슬라이딩 제스처의 슬라이딩 동작을 제어할 수 있다(버튼 프로그래밍을 통해 터치한다).
  • 질문 #1: 감지 및 숨기기


    문제 #1은 매우 간단합니다. Vue2InteractDraggable 구성 요소가 drag*를 초과할 때 interact-out-of-sight-*-coordinate 이벤트를 보내고 구성 요소를 자동으로 숨깁니다.

    질문 2: 카드 접기


    문제2는 상당히 까다롭다.기술적으로 Vue2InteractDraggable는 드래그 가능한 구성 요소일 뿐이다.사용자 인터페이스의 경우 css 구현z-index, widthbox-shadow의 조합을 사용하여 깊이를 시뮬레이션하는 것이 간단할 수 있다.그런데 슬라이딩 부품이 작동할 수 있을까요?응, 나는 부작용을 피하기 위해 맨 밑에 있는 카드pointer-events에 멈출 수 있어.
    한번 해보자.오른쪽으로 미끄러질 때마다 첫 번째 요소가 튀어나올 수 있도록 그룹을 사용합니다.합리적으로 보이죠?
    다음은 지금까지의 코드입니다.
    <template>
      <section class="container">
        <div>
            <Vue2InteractDraggable
              v-for="(card, index) in cards"
              :key="index"
              :interact-out-of-sight-x-coordinate="500"
              :interact-max-rotation="15"
              :interact-x-threshold="200"
              :interact-y-threshold="200"
              @draggedRight="right"
              class="rounded-borders card fixed fixed--center"
              :class="{
                'card--top': index === 0
              }">
              <div class="flex flex--center" style="height: 100%">
                <h1>{{card.text}}</h1>
              </div>
          </Vue2InteractDraggable>
        </div>
      </section>
    </template>
    <script>
    import { Vue2InteractDraggable } from 'vue2-interact'
    
    export default {
      name: 'SwipeableCards',
      components: { Vue2InteractDraggable },
      data() {
        return {
          cards: [
            { text: 'one' },
            { text: 'two' },
            { text: 'three' },
          ]
        }
      },
      methods: {
        right() {
          setTimeout(() => this.cards.shift(), 300);
        }
      }
    }
    </script>
    
    <style lang="scss" scoped>
    .container {
      background: #eceff1;
      width: 100%;
      height: 100vh;
    }
    
    .flex {
      display: flex;
      &--center {
        align-items: center;
        justify-content: center;
      }
    }
    
    .fixed {
      position: fixed;
      &--center {
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
      }
    }
    .rounded-borders {
      border-radius: 2rem
    }
    .card {
      pointer-events: none;
      width: 300px;
      height: 400px;
      &:nth-child(1) {
        background: pink;
        z-index: 3;
      }
      &:nth-child(2) {
        z-index: 2;
        background: red;
        top: 52%;
      }
      &:nth-child(3) {
        z-index: 1;
        background: green;
        top: 54%;
      }
      &--top {
        pointer-events: auto !important;
      }
    }
    </style>
    
    이것이 바로 내가 가진 것이다.

    이것은 완전히 실패다.어떤 이유로 사건이 첫 번째 카드로 자극되었을 때, 그것도 두 번째 카드로 자극되었다.내가 처음 카드를 긁은 후에 DOM에 두 장의 카드만 남은 것을 아래에서 볼 수 있지만, 우리는 두 번째 카드가 보이지 않는다. 왜냐하면 그것은 보이지 않는 곳으로 회전했기 때문이다.dev 도구에서 첫 번째 카드를 슬라이딩한 후에 애니메이션 스타일을 바꾸고 두 번째 카드에 설정하는 것을 볼 수 있습니다. (devtool을 통해 스타일을 사용하지 않을 때 팝업하는 것을 볼 수 있습니다.)

    설령 내가 카드를 한 줄로 배열해 보았다 하더라도 문제는 여전히 존재한다.나는 왜 이러는지 모르겠다.내가 틀림없이 뭔가를 빠뜨렸거나 Vue2InteractDraggable 구성 요소 자체의 문제일 것이다.
    이 점에서 저는 두 가지 선택이 있습니다. 저는 계속 디버깅을 하고 실제 실현을 깊이 연구할 수 있습니다. 원작자의 추출 기능을 거슬러 올라가 서로 다른 점을 찾아내고github repo에서 유사한 문제를 검사하며 그 중에서 답을 찾으려고 시도할 수 있습니다.아니면 다른 방법을 생각해서 같은 일을 하고 다른 시간에 다시 한 번 하세요.
    나는 후자를 선택했다.다른 방법은 첫 번째 방법과 마찬가지로 좋을 수도 있다.이럴 때 나는 너무 많이 물 필요가 없다.나도 다음에 다시 가 볼 수 있다.
    계속합시다.
    이전의 결과는 나로 하여금 생각하게 했다.만약 내가 여러 개 Vue2InteractDraggable 구성 요소를 사용할 때마다 문제가 발생한다면, 왜 이렇게 하는 것을 완전히 피하지 않고 하나만 사용합니까?어쨌든 나는 한 번에 카드 한 장만 끌 수 있다.왜 같은 카드를 사용하고 그에 상응하는 내용을 바꾸지 않습니까?게다가 다른 css가 혼란스러워서 효과가 있을 것 같습니다.
    가장 간단한 코드를 제시하여 나의 가설을 검증합시다.
    <template>
      <section class="container">
        <div class="fixed fixed--center" style="z-index: 3">
          <Vue2InteractDraggable
            v-if="isVisible"
            :interact-out-of-sight-x-coordinate="500"
            :interact-max-rotation="15"
            :interact-x-threshold="200"
            :interact-y-threshold="200"
            @draggedRight="right"
            class="rounded-borders card card--one">
            <div class="flex flex--center" style="height: 100%">
              <h1>{{current.text}}</h1>
            </div>
          </Vue2InteractDraggable>
        </div>
        <div
          class="rounded-borders card card--two fixed fixed--center"
          style="z-index: 2">
          <div class="flex flex--center" style="height: 100%">
            <h1>test</h1>
          </div>
        </div>
        <div
          class="rounded-borders card card--three fixed fixed--center"
          style="z-index: 1">
          <div class="flex flex--center" style="height: 100%">
            <h1>test</h1>
          </div>
        </div>
      </section>
    </template>
    <script>
    import { Vue2InteractDraggable } from 'vue2-interact'
    
    export default {
      name: 'SwipeableCards',
      components: { Vue2InteractDraggable },
      data() {
        return {
          isVisible: true,
          index: 0,
          cards: [
            { text: 'one' },
            { text: 'two' },
            { text: 'three' },
          ]
        }
      },
      computed: {
        current() {
          return this.cards[this.index]
        }
      },
      methods: {
        right() {
          setTimeout(() => this.isVisible = false, 200)
          setTimeout(() => {
            this.index++
            this.isVisible = true
          }, 300)
        }
      }
    }
    </script>
    
    <style lang="scss" scoped>
    .container {
      background: #eceff1;
      width: 100%;
      height: 100vh;
    }
    
    .flex {
      display: flex;
      &--center {
        align-items: center;
        justify-items: center;
        justify-content: center;
      }
    }
    
    .fixed {
      position: fixed;
      &--center {
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
      }
    }
    .rounded-borders {
      border-radius: 12px;
    }
    .card {
      width: 300px;
      height: 400px;
      color: white;
      &--one {
        background-color: pink;
      }
      &--two {
        background-color: red;
        width: 280px;
        top: 51%;
      }
      &--three {
        background-color: orange;
        width: 260px;
        top: 51.8%;
      }
    }
    </style>
    
    
    그리고 성공했어!

    나의 첫 번째 방법과 비교하면 이것은 더욱 간단한 것 같다.나는 그룹의 모든 요소를 실례화하는 것이 아니라 같은 Vue2InteractDraggable 실례를 다시 사용하고 있다.우리는 심지어 모든 카드를 쌓을 필요도 없다. 우리는 단지 이런 착각을 유지할 뿐이다.
    그럼에도 불구하고 저는 첫 번째 원소 뒤에 있는 카드에 다음 원소의 내용을 표시해서 착각을 개선해야 할 것 같습니다. 아래와 같습니다.
    <template>
      <section class="container">
        <div class="fixed fixed--center" style="z-index: 3">
          <Vue2InteractDraggable
            v-if="isVisible"
            :interact-out-of-sight-x-coordinate="500"
            :interact-max-rotation="15"
            :interact-x-threshold="200"
            :interact-y-threshold="200"
            @draggedRight="right"
            class="rounded-borders card card--one">
            <div class="flex flex--center" style="height: 100%">
              <h1>{{current.text}}</h1>
            </div>
          </Vue2InteractDraggable>
        </div>
        <div
          v-if="next"
          class="rounded-borders card card--two fixed fixed--center"
          style="z-index: 2">
          <div class="flex flex--center" style="height: 100%">
            <h1>{{next.text}}</h1>
          </div>
        </div>
        <div
          v-if="index + 2 < cards.length"
          class="rounded-borders card card--three fixed fixed--center"
          style="z-index: 1">
          <div class="flex flex--center" style="height: 100%">
            <h1>test</h1>
          </div>
        </div>
      </section>
    </template>
    <script>
    import { Vue2InteractDraggable } from 'vue2-interact'
    
    export default {
      name: 'SwipeableCards',
      components: { Vue2InteractDraggable },
      data() {
        return {
          isVisible: true,
          index: 0,
          cards: [
            { text: 'one' },
            { text: 'two' },
            { text: 'three' },
          ]
        }
      },
      computed: {
        current() {
          return this.cards[this.index]
        },
        next() {
          return this.cards[this.index + 1]
        }
      },
      methods: {
        right() {
          setTimeout(() => this.isVisible = false, 200)
          setTimeout(() => {
            this.index++
            this.isVisible = true
          }, 300)
        }
      }
    }
    </script>
    
    나도 그에 상응하여 맨 아래의 가상 카드를 숨겼다. 왜냐하면 나는 맨 위의 카드, good'ol switcheroo를 바꾸었기 때문이다.

    이것은 매우 효과가 있다.우리가 위로 이동index할 때 가상 카드를 숨기는 것도 부적과 같다.그림text과 컬러div가 아닌 그림을 사용하기 시작했을 때 더 보기 좋을 수도 있다. 맨 밑에 있는 카드가 맨 위에 있는 카드로 변할 때 미묘한 과도 애니메이션을 놓아 착각을 높일 수도 있다.하지만 나는 잠시 후 이 문제들을 걱정하며 수수께끼의 마지막 부분으로 들어간다.

    질문 #3: 버튼을 클릭하여 카드 사용


    다행히도 이것도 상당히 보잘것없다.vue2-interact는 드래그/슬라이딩 조작을 촉발할 수 있는 EventBus를 공개했다.docs에 따르면 이것은 매우 간단하다. interact-event-bus-events 아이템에 필요한 이벤트를 포함하는 대상을 제공한 다음에 InteractEventBus를 사용하여 필요한 조작을 촉발한다.
    <template>
     <Vue2InteractDraggable
      @draggedLeft="draggedLeft"
      :interact-event-bus-events="interactEventBusEvents"
      v-if="isShowing"
      class="card">
      <div>
        <h3 class="cardTitle">Drag me!</h3>
      </div>
     </Vue2InteractDraggable>
    
     <BaseButton @click="dragLeft" label="⇦" />
    </template>
    <script>
    import { Vue2InteractDraggable, InteractEventBus } from 'vue2-interact'
    const INTERACT_DRAGGED_LEFT = 'INTERACT_DRAGGED_LEFT';
    
    export default {
      components: { Vue2InteractDraggable },
      data() {
        return {
          isShowing: true,
          interactEventBusEvents: {
            draggedLeft: INTERACT_DRAGGED_LEFT,
          },
        };
      },
    
      methods: {
        dragLeft() {
          InteractEventBus.$emit(INTERACT_DRAGGED_LEFT);
        },
      }
    };
    </script>
    
    본질적으로, 우리는 단지 구성 요소에 우리가 draggedLeft $emit 에서 INTERACT_DRAGGED_LEFT 조작을 실행할 때마다 InteractEventBus 이벤트를 터치한다고 알려줄 뿐이다.
    이것들이 있으면, 나는 우리가 필요로 하는 모든 것을 준비했으니, 그것들을 통합하기 시작할 수 있을 것이라고 생각한다.

    이 모든 것을 함께 놓아라


    나는 unsplash에서 몇 개의 그림을 다운로드하고 필요에 따라 사이즈를 줄였다.텍스트를 바꾸고 배경색을 삭제할 수 있도록 이 그림들을 배열의 값으로 사용합니다.나는 만약 내가 카드를 접는 방향을 바꾸면 더욱 간단하게 착각을 강화할 수 있다는 것을 깨달았다.나는 그것을 위로 개지 않고 비스듬히 개었다.이렇게 하면 나의 과도 애니메이션은 두 번째 카드의 x와 y를 간단하게 이동할 수 있고 전환이 발생할 때 첫 번째 카드에 놓을 수 있다.나는 내가 취한 모든 절차를 보여줌으로써 너를 짜증나게 하지 않을 것이다. 나는 네가 이미 이 생각을 이해했다고 생각한다. 나는 그것을 너의 상상에 남겨 둘 것이다.
    css 마법, 점차적 변화, 음영 및 기타 것을 첨가한 후.구글 글씨체와 일부 재질 아이콘.나의 결말은 이렇다.

    봐라, 키틴드!고양이용 성냥.이게 의미가 있나요?몰라요.이것은 쌍관어의 기회다.만약 이것이 진정한 응용 프로그램이라면, 내 고양이가 카트리나 허리케인에서 긁을 수도 있고, 그들의 나이 차이가 많지 않아서, 나는 고양이들이 잘 어울릴 것이라고 생각한다.
    이github 저장소 kittynder 에서 전체 코드를 볼 수 있습니다.나는 넷리프kittynder.netlify.com에서 시범을 발표했다.이동 뷰포트에서 보는 것이 좋습니다.

    후기


    이 간단한 활동에서, 나는 오늘 이 전환 가능한tinder와 유사한 UI를 구축하는 것이 얼마나 쉬운지 깨달았다.나는 단지 두 시간도 안 걸려서 완성했다.현재 인터넷의 도구와 자원은 그 어느 때보다 많아서 많은 것을 구축할 수 있다. 이런 것들은 이전에 당신의 능력 범위를 훨씬 벗어난 것 같다.이것이 바로 개원 지역사회의'u,l,t,r,a,i,n,s,t,i,n,c,t'의 힘이다.이것도 내가 이런 강좌를 쓰기 시작한 원인 중의 하나다.이것은 내가 지역 사회에 보답하는 방식이다.나는 단지 평범한 개발자일 수도 있지만, 나의 사고 과정과 문제를 해결하는 방법은 막 시작한 사람 (그리고 미래의 나에게 있어서 1년 후에 나는 모든 것을 완전히 잊을 것이다) 에게 가치가 있을 것이다.

    다음 단계는 무엇입니까?


    물론 이것은 결코 생산 준비가 다 된 것이 아니다.제 css 게임은 매우 엉망입니다. tailwind.css 같은 것을 사용하고, 이미지를 미리 캐시하고, 브라우저의 호환성을 검사해야 할 수도 있습니다.하지만, 헤헤, 이것은 아주 좋은 단련이다.한 걸음 한 걸음 너는 결국 그곳에 도착할 것이다.검색, 읽기, 구축만 하면 됩니다.
    실제로 저는 조금 큰 개인 프로젝트에서 Quasar Framework를 사용하여 비슷한 기능을 실현하고 있지만 이것은 또 다른 시대의 이야기입니다.

    유용한 링크

  • Kittynder Demo
  • Kittynder Project Repository
  • Swipeable Card Stack Using Vue
  • vue2-interact
  • vue2-interact Documentation
  • 이 글은 최초로 나의 개인 홈페이지website에 발표되었다.

    좋은 웹페이지 즐겨찾기