Toss renewal

하늘·2022년 9월 3일
2

클론코딩

목록 보기
5/5
post-thumbnail

Toss renewal (반응형)

📎 https://github.com/eveneul/toss
📎 https://eveneul.github.io/toss

awwwards에 올라온 수상작을 레이아웃 레퍼런스로 참고하고, 기본적인 테마 색상이나 분위기는 토스 공식 홈페이지를 참고했습니다

1. Media Query

먼저 SCSS로 스타일 작업을 했기 때문에 @media screen을 쓰기보다는 @mixin를 사용해서 처리했습니다

$tablet: 1024px;
$mobile: 768px;

/*반응형, 브라우저 크기가 767px 이하일때*/
@mixin mobile {
	@media (max-width: $mobile) {
		@content;
	}
}

/*반응형, 브라우저 크기가 768이상, 1023px 이하일때*/
@mixin tablet {
	@media (max-width: $tablet) {
		@content;
	}
}

분기점을 변수로 만들고, @mixin을 통해 CSS 변화가 필요한 부분마다 @include를 사용해 주었습니다

	.menu-list {
		display: flex;

		@include mobile {
			flex-direction: column;
		}
	}

SCSS로 mixin을 잘 사용하지 못할 때에는 @media screen을 이용해 모든 스타일을 복사해서 붙여넣기를 하고 필요한 부분만 수정했는데 mixin으로 미디어쿼리를 사용하니까 간편하고 무엇보다 헷갈리지 않아서 더 좋았습니다
(그전에는 뭘 바꿨는지 헷갈렸었음..)

2. HEADER

📱 Mobile ver.


화질구지..

  1. 햄버거 메뉴를 만들어서 모바일 버전에는 노출되도록 했습니다.
  2. Nav 태그를 재활용해서 햄버거 메뉴를 위한 Nav를 따로 작성하지 않았습니다
  3. 햄버거 메뉴를 클릭해 nav가 노출되었을 때는 스크롤(드래그)를 금지시켰습니다

.m-btn이라는 햄버거 버튼을 만들어서 클릭했을 시 .active라는 클래스가 생성됩니다
그리고 이 .active 태그의 유무를 확인하기 위해 hasClass를 사용해
.active 클래스가 있으면(햄버거 버튼 활성화) overflow: hidden으로 드래그(스크롤)를 금지시켰고
그게 아니라면 드래그(스크롤)이 가능하도록 설정했습니다

	$('.m-btn').click(function () {
		$(this).toggleClass('active');
		$('.header').toggleClass('active');
		if ($('.header').hasClass('active')) {
			$('html, body').css({ overflow: 'hidden', height: '100%' });
		} else {
			$('html, body').css({ overflow: 'auto', height: 'auto' });
		}
	});

3. Main(container)

🔥 sc-about

  1. 드래그하여 해당 섹션이 가운데에 위치했을 시 남색의 배경 width가 100%가 되고, 다시 드래그를 하면 줄어듭니다
	const bgEffect = gsap.timeline({
		scrollTrigger: {
			trigger: '.sc-about',
			start: 'top bottom',
			end: 'bottom 20%',
			scrub: true,
		},
	});

	bgEffect
		.to('.sc-about .bg', {
			'width': '100%',
			'border-radius': 0,
		})
		.to('.sc-about .bg', {
			'width': '100%',
			'border-radius': 0,
		})
		.to('.sc-about .bg', {
			'width': '80%',
			'border-radius': 30,
		});

gsap의 timeline을 통해서 만들었습니다

  1. 드래그에 따라서 누적 가입자 수, 누적 투자 금액 등 숫자가 0에서 지정된 숫자까지 올라갑니다
	function format_number(x) {
		return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
	}

천 단위로 콤마를 찍어 주고, 0부터 미리 지정해 둔 숫자까지 카운트 되면서 올라가는 함수를 만들었습니다

	$('.about-item .num').each(function (idx, el) {
		const value = {
			val: $(this).text(),
		};

		const numEffect = gsap.timeline({
			scrollTrigger: {
				trigger: el,
				start: 'top 80%',
				end: '+=100',
			},
		});
		numEffect
			.addLabel('a')
			.from(
				value,
				{
					duration: 2,
					ease: 'circ.out',
					val: 0,
					roundProps: 'val',
					onUpdate: function () {
						el.innerText = format_number(value.val);
					},
				},
				'a'
			)
			.from(
				$(this).parents('.about-item'),
				{
					opacity: 0,
					y: 50,
				},
				'a'
			);
	});

gsap timeline으로 드래그함에 따라서 올라오고, onUpdate를 통해 카운트 함수가 실행되도록 작성했습니다

🔥 sc-vision(드래그하면 가로로 이동)

gsap을 통해 드래그하면 가로에 있는 요소들이 움직이며 보이게끔 하는 효과를 구현했습니다!
(꼭 한번쯤 해보고 싶었던 거..)

	let totalWidth = 0;
	const visionEls = $('.vision-item');
	const visionElWidth = $('.vision-item').innerWidth();
	const visionItemW = $('.vision-item').width() / 2;

	totalWidth = (visionEls.length * visionElWidth) / 2 - visionItemW;

	basePosition();

	gsap.to('.vision-list', {
		x: -totalWidth,
		scrollTrigger: {
			trigger: '.sc-vision',
			start: 'bottom bottom',
			end: '+=300%',
			scrub: true,
			pin: true,
		},
	});

	function basePosition() {
		const innerW = $('.sc-vision .inner-s').width() / 2;

		gsap.set('.vision-list', {
			x: innerW - visionItemW,
		});
	}

스크롤하기 전 첫 번째 요소가 화면의 정중앙에 위치시켜 주기 위해서 아이템의 width 값을 구하고 2로 나뉘었습니다

그 값을 아이템을 감싼 ul에게 ((아이템 개수 * 아이템 각각의 width) / 2) - 아이템 width를 반으로 나눈 값을 적용해 주었습니다

(글로 설명하려니까 잘 안 됨..)

또 그값을 ul의 x로 적용시켜서 scrollTrigger로 스크롤할 때 ul이 움직이게끔 작성했습니다

특히 basePosition 함수는 호이스팅 개념 덕분에 선언보다 위에서 호출했습니다 (상관없음)

🔥 sc-investor(canvas 활용)

애플 사이트에 단골로 나오는 스크롤할 때마다 재생되는 이미지 기능을 구현했습니다

        <canvas class="canvas"></canvas>

롤링되는 내용물들의 배경으로 사용할 것이기 때문에 안에 내용물은 비웠습니다!

	const canvas = document.querySelector('canvas');
	const ctx = canvas.getContext('2d');

	canvas.width = 1200;
	canvas.height = 675;

canvas는 바닐라 자바스크립트로 작성했습니다

canvas.getContext로 해당 캔버스가 2d로 작업할 거라고 미리 알려주고 canvas.width와 canvas.height로 사이지를 적용시켜 주었습니다

	const frameCount = 100;

	const currentFrame = (idx) => {
		return `asset/images/capture/capture${idx.toString()}.jpg`;
	}; // 리턴 필수

frameCount는 화면에 출력할 이미지의 총 개수이고
currentFrame으로 asset 폴더에 있는 이미지들 전체를 불러왔습니다
계속 undefined로 오류가 나길래 이리저리 고치다가 결국 return을 쓰고 안 쓰고의 차이라는 걸 알고
감격스러워서 울 뻔했네요.. return은 필수!

	const images = [];
	const card = {
		frame: 0,
	};

	for (let i = 0; i < frameCount; i++) {
		const img = new Image();
		img.src = currentFrame(i + 1);
		images.push(img);
	}

반복문을 통해서 불러온 이미지들을 images라는 배열에 담았습니다

	gsap.to(card, {
		frame: frameCount - 1,
		snap: 'frame',
		ease: 'none',
		scrollTrigger: {
			trigger: '.sc-investor',
			scrub: 0.5,
			start: 'top 90%',
			end: 'bottom center',
		},
		onUpdate: render,
	});

	images[0].onload = render;

	function render() {
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		ctx.drawImage(images[card.frame], 0, 0);
	}

gsap scrollTrigger로 드래그에 따라서 사진이 바뀌게 frame으로 설정하고,
onUpdate로 드래그할 때마다 함수가 실행되는데,
render 함수는 2d로 만들어진 canvas에 이미지가 출력되도록 작성했습니다

반응형도 그렇고 그동안 레퍼런스를 보면서 해 보고 싶은 기능 구현들을 해 볼 수 있어서 재미있었습니다
반응형 구현이 어렵지 않을 거라 생각했던 나의 오만..을 흠뻑 때려 준 토스 리뉴얼 코딩 ㅠ.ㅠ
반응형은 여러번 더 공부하고 복습해야 할 것 같습니다

0개의 댓글