티스토리 뷰
observer를 이용한스크롤
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>observer를 이용한스크롤</title>
<style>
.box { border: 1px solid #000;}
.show { border-color: red;}
</style>
</head>
<body>
<div>
<div class="box" style="height: 500px;"></div>
<div class="box" style="height: 500px;"></div>
<div class="box" style="height: 500px;"></div>
<div class="box" style="height: 500px;"></div>
<div class="box" style="height: 500px;"></div>
<div class="box" style="height: 300px;"></div>
<div class="box" style="height: 300px;"></div>
<div class="box" style="height: 300px;"></div>
<div class="box" style="height: 300px;"></div>
<div class="box" style="height: 300px;"></div>
</div>
<script>
let option = {
threshold: 1 /* 보이는 영역 비율 0 ~ 1 */
}
let observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if(entry.isIntersecting) {
entry.target.classList.add('show');
} else {
entry.target.classList.remove('show');
}
});
}, option);
const boxElemList = document.querySelectorAll('.box');
boxElemList.forEach(elem => observer.observe(elem));
</script>
</body>
</html>
observer를 이용한 Lazy load 이미지
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>observer를 이용한 Lazy load 이미지</title>
<style>
.box { border: 2px solid #000;}
.show { border-color: red;}
</style>
</head>
<body>
<div>
<div class="box" style="height: 500px;"></div>
<div class="box" style="height: 500px;"></div>
<div class="box" style="height: 500px;"></div>
<div class="box" style="height: 500px;"></div>
<div class="box images" style="height: 300px; border-color: blue;">
<div class="img"></div>
</div>
<div class="box" style="height: 500px;"></div>
<div class="box" style="height: 300px;"></div>
<div class="box" style="height: 300px;"></div>
<div class="box" style="height: 300px;"></div>
<div class="box" style="height: 300px;"></div>
<div class="box images" style="height: 300px; border-color: blue;">
<div class="img"></div>
</div>
<div class="box" style="height: 300px;"></div>
</div>
<script>
let option = {
threshold: 0 /* 보이는 영역 비율 0 ~ 1 */
}
let observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if(!entry.isIntersecting) return;
const target = entry.target;
const src = target.dataset.src;
target.querySelector('.img').style.backgroundImage = `url(https://image/${src}.com)`;
// 로드가 완료되면 더 이상 로딩 관리를 할 필요가 없기 때문에
// 해당 요소에 한해 관측을 해지
observer.unobserve(target);
//해당 요소에 한해 관측을 종료
// observer.disconnect(target);
});
}, option);
document.querySelectorAll('.images').forEach(elem => observer.observe(elem));
</script>
</body>
</html>
찹쌀떡효과
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>찹쌀떡효과</title>
<style>
.svg { position: absolute; left: calc(50% - 200px); top: calc(50% - 150px); width: 400px; height: 300px; margin: auto; filter: url(#gooey);}
.circle { position: absolute; left: 50%; top: 50%; width: 100px; height: 100px; margin: -50px 0 0 -50px; background-color: #60cb60; border-radius: 100%;}
.circle:last-child { width: 50px; height: 50px; background-color: red; animation: circle-right ease-in-out 20s infinite alternate;}
@keyframes circle-right {
0% { transform: translate(-10px, -60px);}
40% { transform: translate(57px, 120px);}
60% { transform: translate(-20px, 120px);}
100% { transform: translate(57px, -60px);}
}
</style>
</head>
<body>
<div class="svg">
<div class="circle"></div>
<div class="circle"></div>
</div>
<svg>
<defs>
<filter id="gooey">
<!-- feGaussianBlur in="효과적용대상" stdDeviation="블러수치" result="결과를변수에저장" -->
<feGaussianBlur in="SourceGraphic" stdDeviation="12" result="blur" />
<feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 70 -9" result="goo" />
<feComposite in="SourceGraphic" in2="goo" operator="atop" />
</filter>
</defs>
</svg>
</body>
</html>
스와이프 샘플
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css"/>
<script src="https://code.jquery.com/jquery-latest.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"></script>
<title>스와이퍼 샘플</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box;}
.swiper-slide { height: 150px; border: 1px dashed #000;}
.control { border: 1px dashed #000;}
</style>
</head>
<body>
<div class="swiper">
<div class="swiper-container">
<div class="swiper-wrapper">
<div class="swiper-slide">
<a href="#" role="button"><p>첫번째</p></a>
</div>
<div class="swiper-slide">
<a href="#" role="button"><p>두번째</p></a>
</div>
</div>
</div>
<div class="position">
<div class="fraction">
<span class="fraction__active">1</span> / <span class="fraction__total">2</span>
</div>
<a href="#" class="control" role="button"><span class="blind">정지</span></a>
</div>
</div>
<script>
$(function() {
var swiper = new Swiper('.swiper .swiper-container', {
autoplay: {
disableOnInteraction: false,
},
loop: true,
observer: true,
observeParents: true,
on: {
slideChangeTransitionEnd: function() {
var totalSlides = $('.swiper-slide:not(.swiper-slide-duplicate)').length;
var $slideActive = $('.swiper-slide-active');
var realIndex = $slideActive.data('swiper-slide-index');
if(typeof realIndex === 'undefined') {
realIndex = $slideActive.index();
}
$('.fraction__active').html(realIndex + 1);
$('.fraction__total').html(totalSlides);
}
}
});
$('.control').on('click', function() {
if(!$(this).hasClass('play')) {
swiper.autoplay.stop();
$(this).children('span').html('재생');
$(this).addClass('play');
} else {
swiper.autoplay.start();
$(this).children('span').html('정지');
$(this).removeClass('play');
}
})
});
</script>
</html>
토스트팝업
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>토스트팝업</title>
<script src="http://code.jquery.com/jquery-latest.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box;}
.toast { position: fixed; left: 10px; right: 10px; bottom: 10px; z-index: 100; background-color: #fff;}
.dimmed--toast { position: fixed; left: 0; right: 0; top: 0; bottom: 0; z-index: 99; background-color: rgba(0, 0, 0, .4);}
</style>
</head>
<body>
<div id="Wrap">
<a href="#" class="btn" data-message="0">토스트 팝업1</a>
<a href="#" class="btn" data-message="1">토스트 팝업2</a>
</div>
<script>
$(function() {
$('.btn').on('click', function() {
var messageIndex = $(this).data('message');
var left = {
p0: '첫번째 토스트팝업 메세지',
p1: '두번째 토스트팝업 메세지',
}
$.toast({
message: left['p'+messageIndex]
});
});
$.toast({
message: '새로고침시 나타나는 메세지',
});
});
</script>
<script>
var toastConfig = {
autoDismiss: true,
autoDismissDelay: 2400,
transitionDuration: 200,
}
$.toast = function(config) {
config = {
message: config.message,
btn: event.currentTarget,
type: event.type,
}
return new toast(config);
}
var toast = function(config) {
config = $.extend({}, toastConfig, config);
var toast = $([
'<div class="toast" role="status" aria-live="polite" tabindex="0">',
'<p class="toast__titi">'+ config.message +'</p>',
'<button type="button">테스트 버튼</button>',
'<a href="#">테스트 앵커</a>',
'<button type="button" class="toast__close">닫기</button>',
'</div>',
].join(''));
if(config.type === 'click') {
config.btn.focus();
}
toast.find('.toast__close').on('click', function() {
var toast = $(this).closest('.toast');
toast.remove();
$('.dimmed--toast').remove();
$('#Wrap').removeAttr('aria-hidden');
setTimeout(function() {
toast.remove();
if(config.type === 'click') {
config.btn.focus();
}
}, config.transitionDuration);
});
toast.appendTo(document.body);
toast.before('<div class="dimmed--toast"></div>')
$('#Wrap').attr('aria-hidden', 'true');
setTimeout(function() {
toast.addClass('show');
goFocus($('.toast'));
focusRotation($('.toast'));
}, config.transitionDuration);
if(config.autoDismiss) {
setTimeout(function() {
toast.find('.toast__close').click();
}, config.autoDismissDelay)
}
return this;
}
goFocus = function(tgEl) {
var $tgEl = $(tgEl);
$tgEl.focus();
}
focusRotation = function(tgEl) {
var $tgWrap = $(tgEl),
tabbableEl = '[href], button, input, select, textarea, [tabindex]:not([tabindex="-1"])',
focusTg = $tgWrap.find(tabbableEl),
$firstFocusEl = focusTg.first(),
$lastFocusEl = focusTg.last();
$lastFocusEl.on({
'click': function() {
$tgWrap.find('.toast__close').click();
},
'keydown': function(e) {
var _keycode = e.keyCode || e.which;
if(_keycode === 9 && !e.shiftKey) {
e.preventDefault();
goFocus($firstFocusEl);
}
},
});
}
</script>
</body>
</html>
프레스 효과
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>프레스 효과</title>
<script src="http://code.jquery.com/jquery-latest.min.js"></script>
<style>
.list { display: flex;}
a { display: block; width: 150px; height: 150px; border: 1px solid #000; transition: transform .2s ease-out;}
a.press { background-color: #eee; transform: scale(0.93);}
</style>
</head>
<body>
<div class="list">
<a href="#">눌러주세요1</a>
<a href="#">눌러주세요2</a>
</div>
<script>
const touchConf = {};
$('.list a').on('touchstart touchmove touchend', function(e) {
switch(e.type) {
case 'touchstart':
touchConf.y = e.originalEvent.changedTouches[0].pageY;
touchConf.scrolled = false;
e.currentTarget.classList.add('press');
break;
case 'touchmove':
if(e.originalEvent.changedTouches[0].pageY != touchConf.y) {
touchConf.scrolled = true;
e.currentTarget.classList.remove('press');
}
break;
case 'touchend':
if(touchConf.scrolled == false) {
e.currentTarget.classList.remove('press');
}
break;
}
});
</script>
</body>
</html>
스크롤이동콤포넌트
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>스크롤이동콤포넌트</title>
<script src="https://code.jquery.com/jquery-latest.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box;}
.flexGroup { display: flex; flex-direction: column; position: fixed; left: 0; right: 0; top: 0; bottom: 0;}
.btnGroup { position: relative; height: 42px;}
.btns { overflow-x: auto; overflow-y: hidden; display: flex; padding: 10px 10px 10px 0; white-space: nowrap;}
.btn { margin-left: 10px;}
.btn.on { color: #fff; background-color: red;}
.boxGroup { overflow: auto; flex: 1; padding: 10px;}
.box { position: relative;}
.box li { border: 1px solid black;}
.box li.on { border: 2px solid red;}
</style>
</head>
<body>
<div class="flexGroup">
<div class="btnGroup">
<div class="btns">
<button type="button" class="btn on">버튼111</button>
<button type="button" class="btn">버튼2222</button>
<button type="button" class="btn">버튼3333</button>
<button type="button" class="btn">버튼444444444</button>
<button type="button" class="btn">버튼555555</button>
<button type="button" class="btn">버튼6666</button>
<button type="button" class="btn">버튼7777777</button>
</div>
</div>
<div class="boxGroup">
<ul class="box">
<li class="on" style="height: 600px;">박스1</li>
<li style="height: 400px;">박스2</li>
<li style="height: 500px;">박스3</li>
<li style="height: 600px;">박스4</li>
<li style="height: 300px;">박스5</li>
<li style="height: 500px;">박스6</li>
<li style="height: 400px;">박스7</li>
</ul>
</div>
</div>
<script>
class overTab {
constructor(idx) {
this.idx = idx;
}
tabClick() {
$('.btn').removeClass('on').eq(this.idx).addClass('on');
let wid = $('.btn.on').outerWidth() / 2;
let sLft = $('.btns').scrollLeft();
let lft = $('.btn.on').position().left;
let boxWid = $('.btns').outerWidth() / 2;
let resLft = (lft + sLft) - (boxWid - wid - 10);
$('.btns').stop().animate({scrollLeft: resLft}, 200);
}
overScroll() {
$('.box li').each(function(idx, elm) {
let elmTop = $(elm).position().top;
let boxScrollTop = $('.boxGroup').scrollTop();
let boxHeight = $(elm).height();
if(elmTop <= boxScrollTop + 100 && elmTop + boxHeight > boxScrollTop + 100) {
$(elm).addClass('on');
} else {
$(elm).removeClass('on');
}
});
/* 바닥체크 */
const liLength = $('.box li').length;
let sTop = $('.boxGroup').scrollTop();
let sinnerHeight = $('.boxGroup').innerHeight();
let sHeight = $('.boxGroup').prop('scrollHeight');
if(sTop + sinnerHeight >= sHeight) {
this.idx = liLength - 1;
$('.box li').removeClass('on').last().addClass('on');
} else {
this.idx = $('.box li.on').index();
}
this.tabClick();
}
}
$('.btn').on('click', function() {
let idx = $(this).index();
let boxTop = $('.box li').eq(idx).position().top;
$('.boxGroup').stop().animate({scrollTop: boxTop}, 300);
const playEvent = new overTab(idx);
playEvent.tabClick();
});
$('.boxGroup').scroll(function() {
const playEvent = new overTab();
playEvent.overScroll();
});
</script>
</body>
</html>