Vue 3와 Element Plus를 활용한 반응형 그리드 레이아웃 구현

Element Plus 그리드 시스템 개요

Vue 3 환경에서 Element Plus는 24개의 열(Column)을 기반으로 하는 유연한 그리드 레이아웃 시스템을 제공합니다. 이를 통해 복잡한 UI도 쉽게 구조화할 수 있으며, el-rowel-col 컴포넌트를 조합하여 다양한 화면 크기에 대응하는 반응형 디자인을 구축할 수 있습니다.

el-row 컴포넌트 주요 속성

행(Row)을 정의하는 el-row는 열들의 배치 방식과 간격을 제어합니다.

  • gutter: 열 사이의 간격(픽셀 단위)을 지정합니다. 기본값은 0입니다.
  • type: 레이아웃 모드를 설정합니다. flex를 사용하여 플렉스박스 레이아웃을 활성화할 수 있습니다.
  • justify: type="flex"일 때 수평 정렬 방식을 결정합니다 (예: start, center, end, space-between).
  • align: type="flex"일 때 수직 정렬 방식을 결정합니다 (예: top, middle, bottom).
  • tag: 렌더링될 사용자 정의 HTML 태그를 설정합니다. 기본값은 div입니다.

el-col 컴포넌트 주요 속성

열(Column)을 정의하는 el-col은 각 요소가 차지하는 너비와 위치를 조절합니다.

  • span: 열이 차지하는 칸 수를 지정합니다 (최대 24). 기본값은 24입니다.
  • offset: 열 왼쪽에 여백으로 남길 칸 수를 설정합니다.
  • push / pull: 열을 오른쪽이나 왼쪽으로 이동시켜 순서를 변경할 때 사용합니다.
  • 반응형 속성 (xs, sm, md, lg, xl): 다양한 뷰포트 너비에 따라 동적으로 span이나 offset을 적용할 수 있습니다. 숫자 또는 객체(예: {span: 4, offset: 2})를 전달합니다.
    • xs: 768px 미만
    • sm: 768px 이상
    • md: 992px 이상
    • lg: 1200px 이상
    • xl: 1920px 이상

기본 그리드 레이아웃 예제

다음은 20px의 간격을 두고 화면을 3등분하는 기본적인 그리드 구조입니다.

<template>
  <el-row :gutter="20">
    <el-col :span="8">
      <div class="grid-content">섹션 A</div>
    </el-col>
    <el-col :span="8">
      <div class="grid-content">섹션 B</div>
    </el-col>
    <el-col :span="8">
      <div class="grid-content">섹션 C</div>
    </el-col>
  </el-row>
</template>

<style scoped>
.grid-content {
  background-color: #f0f2f5;
  padding: 20px;
  text-align: center;
  border-radius: 4px;
}
</style>

반응형 레이아웃을 적용한 로그인 폼 구현

그리드 시스템을 활용하여 화면 크기에 따라 중앙 정렬되는 반응형 로그인 폼을 만들어 보겠습니다. Vue 3의 <script setup> 문법을 사용하여 코드를 간결하게 작성했습니다.

<template>
  <el-row justify="center" class="login-container">
    <el-col :xs="24" :sm="18" :md="12" :lg="8">
      <el-card shadow="hover">
        <template #header>
          <h3 style="text-align: center; margin: 0;">시스템 로그인</h3>
        </template>
        
        <el-form 
          :model="credentials" 
          :rules="validationRules" 
          ref="authFormRef" 
          label-position="top"
        >
          <el-form-item label="아이디" prop="userId">
            <el-input 
              v-model="credentials.userId" 
              prefix-icon="User" 
              placeholder="아이디를 입력하세요" 
            />
          </el-form-item>
          
          <el-form-item label="비밀번호" prop="userPw">
            <el-input 
              v-model="credentials.userPw" 
              type="password" 
              prefix-icon="Lock" 
              placeholder="비밀번호를 입력하세요" 
              show-password 
            />
          </el-form-item>
          
          <el-form-item label="보안 문자" prop="securityCode">
            <el-row :gutter="10">
              <el-col :span="16">
                <el-input 
                  v-model="credentials.securityCode" 
                  placeholder="문자를 입력하세요" 
                />
              </el-col>
              <el-col :span="8">
                <img 
                  :src="captchaImageUrl" 
                  alt="Captcha" 
                  class="captcha-img" 
                  @click="regenerateCaptcha" 
                />
              </el-col>
            </el-row>
          </el-form-item>
          
          <el-form-item>
            <el-button type="primary" style="width: 100%;" @click="handleLogin">
              로그인
            </el-button>
          </el-form-item>
        </el-form>
      </el-card>
    </el-col>
  </el-row>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import { ElMessage } from 'element-plus';

const authFormRef = ref(null);

const credentials = ref({
  userId: '',
  userPw: '',
  securityCode: '',
});

const validationRules = {
  userId: [{ required: true, message: '아이디는 필수 입력 항목입니다.', trigger: 'blur' }],
  userPw: [
    { required: true, message: '비밀번호를 입력해 주세요.', trigger: 'blur' },
    { min: 6, message: '비밀번호는 최소 6자 이상이어야 합니다.', trigger: 'blur' }
  ],
  securityCode: [{ required: true, message: '보안 문자를 입력하세요.', trigger: 'blur' }],
};

const captchaImageUrl = ref('');

const regenerateCaptcha = () => {
  const timestamp = Date.now();
  captchaImageUrl.value = `/api/v1/auth/captcha?_t=${timestamp}`;
};

const handleLogin = async () => {
  if (!authFormRef.value) return;
  
  try {
    await authFormRef.value.validate();
    ElMessage.success('인증 성공! 대시보드로 이동합니다.');
    // 실제 API 호출 로직 수행
  } catch (error) {
    ElMessage.error('입력 값을 다시 확인해 주세요.');
  }
};

onMounted(() => {
  regenerateCaptcha();
});
</script>

<style scoped>
.login-container {
  margin-top: 50px;
}
.captcha-img {
  width: 100%;
  height: 32px;
  cursor: pointer;
  border-radius: 4px;
  border: 1px solid #dcdfe6;
}
</style>

위 코드에서는 el-rowjustify="center" 속성을 활용해 폼을 화면 중앙에 배치했습니다. 또한 el-col에 반응형 속성(:xs, :sm, :md, :lg)을 적용하여 모바일부터 데스크톱 환경까지 최적화된 너비를 자동으로 계산하도록 구현했습니다. 보안 문자 입력 영역에서는 중첩된 el-rowel-col을 사용하여 입력창과 이미지를 비율에 맞게 분할했습니다.

태그: vue3 element-plus el-row el-col grid-layout

6월 2일 20:47에 게시됨