svelte-scrollable-card
Version:
Simple custom components for scrollable cards
421 lines (353 loc) • 11 kB
HTML
<script>
import {fly} from 'svelte/transition';
import ScrollItem from './ScrollItem.svelte';
import {onDestroy, onMount} from 'svelte';
let left = 0;
let list;
onMount(() => {
left = list.scrollLeft;
window.addEventListener('resize', () => {
if (left !== list.scrollLeft) {
left = list.scrollLeft;
}
});
});
const myInterval = setInterval(() => {
if (left !== list.scrollLeft) {
left = list.scrollLeft;
}
}, 500);
onDestroy(() => {
clearInterval(myInterval);
});
function moveLeft() {
list.scrollTo({
left: list.scrollLeft - window.innerWidth - 21,
behavior: 'smooth',
});
left = list.scrollLeft;
}
function moveRight() {
list.scrollTo({
left: list.scrollLeft + window.innerWidth - 21,
behavior: 'smooth',
});
left = list.scrollLeft;
}
$: leftEnable = list ? left !== 0 : false;
$: leftRight = list ? left < (list.scrollWidth - list.clientWidth) : false;
</script>
<style>
.horizontal-list-container {
display: flex;
align-content: center;
width: calc(100vw - 20px);
margin-bottom: -5px;
animation-name: fadeIn;
animation-duration: 0.2s;
animation-fill-mode: both;
}
.horizontal-list-container .skip-button {
position: absolute;
align-self: center;
z-index: 99999;
margin-top: 10px ;
}
@media (max-width: 600px) {
.horizontal-list-container .skip-button {
display: none;
}
}
.horizontal-list-container .skip-button.start {
left: 0;
}
.horizontal-list-container .skip-button.end {
right: 0;
}
.horizontal-list-container .items {
overflow: hidden;
transition-duration: 0.15s;
transition-timing-function: cubic-bezier(0.4, 0, 1, 1);
will-change: transform;
display: inline-block;
white-space: nowrap;
padding: 14px 14px 0;
}
@media (max-width: 600px) {
.horizontal-list-container .items {
overflow: auto;
}
}
@keyframes fadeIn {
0% {
opacity: 0;
transform: translateY(100%);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.mdc-fab {
box-shadow: 0px 3px 5px -1px rgba(0, 0, 0, 0.2), 0px 6px 10px 0px rgba(0, 0, 0, 0.14), 0px 1px 18px 0px rgba(0, 0, 0, 0.12);
display: inline-flex;
position: relative;
align-items: center;
justify-content: center;
box-sizing: border-box;
width: 56px;
height: 56px;
padding: 0;
border: none;
fill: currentColor;
cursor: pointer;
user-select: none;
-moz-appearance: none;
-webkit-appearance: none;
overflow: hidden;
transition: box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1), opacity 15ms linear 30ms, transform 270ms 0ms cubic-bezier(0, 0, 0.2, 1);
background-color: #018786;
color: #fff;
/* @alternate */
color: var(--mdc-theme-on-secondary, #fff);
}
.mdc-fab:not(.mdc-fab--extended) {
border-radius: 50%;
}
.mdc-fab::-moz-focus-inner {
padding: 0;
border: 0;
}
.mdc-fab:hover, .mdc-fab:focus {
box-shadow: 0px 5px 5px -3px rgba(0, 0, 0, 0.2), 0px 8px 10px 1px rgba(0, 0, 0, 0.14), 0px 3px 14px 2px rgba(0, 0, 0, 0.12);
}
.mdc-fab:active {
box-shadow: 0px 7px 8px -4px rgba(0, 0, 0, 0.2), 0px 12px 17px 2px rgba(0, 0, 0, 0.14), 0px 5px 22px 4px rgba(0, 0, 0, 0.12);
}
.mdc-fab:active, .mdc-fab:focus {
outline: none;
}
.mdc-fab:hover {
cursor: pointer;
}
.mdc-fab > svg {
width: 100%;
}
@supports not (-ms-ime-align: auto) {
.mdc-fab {
/* @alternate */
background-color: var(--mdc-theme-secondary, #018786);
}
}
.mdc-fab .mdc-fab__icon {
width: 24px;
height: 24px;
font-size: 24px;
}
.mdc-fab--mini {
width: 40px;
height: 40px;
}
.mdc-fab--extended {
font-family: Roboto, sans-serif;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
font-size: 0.875rem;
line-height: 2.25rem;
font-weight: 500;
letter-spacing: 0.08929em;
text-decoration: none;
text-transform: uppercase;
border-radius: 24px;
padding: 0 20px;
width: auto;
max-width: 100%;
height: 48px;
}
.mdc-fab--extended .mdc-fab__icon {
/* @noflip */
margin-left: -8px;
/* @noflip */
margin-right: 12px;
}
[dir="rtl"] .mdc-fab--extended .mdc-fab__icon, .mdc-fab--extended .mdc-fab__icon[dir="rtl"] {
/* @noflip */
margin-left: 12px;
/* @noflip */
margin-right: -8px;
}
.mdc-fab--extended .mdc-fab__label + .mdc-fab__icon {
/* @noflip */
margin-left: 12px;
/* @noflip */
margin-right: -8px;
}
[dir="rtl"] .mdc-fab--extended .mdc-fab__label + .mdc-fab__icon, .mdc-fab--extended .mdc-fab__label + .mdc-fab__icon[dir="rtl"] {
/* @noflip */
margin-left: -8px;
/* @noflip */
margin-right: 12px;
}
.mdc-fab__label {
justify-content: flex-start;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.mdc-fab__icon {
transition: transform 180ms 90ms cubic-bezier(0, 0, 0.2, 1);
fill: currentColor;
will-change: transform;
}
.mdc-fab .mdc-fab__icon {
display: inline-flex;
align-items: center;
justify-content: center;
}
.mdc-fab--exited {
transform: scale(0);
opacity: 0;
transition: opacity 15ms linear 150ms, transform 180ms 0ms cubic-bezier(0.4, 0, 1, 1);
}
.mdc-fab--exited .mdc-fab__icon {
transform: scale(0);
transition: transform 135ms 0ms cubic-bezier(0.4, 0, 1, 1);
}
@keyframes mdc-ripple-fg-radius-in {
from {
animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transform: translate(var(--mdc-ripple-fg-translate-start, 0)) scale(1);
}
to {
transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
}
}
@keyframes mdc-ripple-fg-opacity-in {
from {
animation-timing-function: linear;
opacity: 0;
}
to {
opacity: var(--mdc-ripple-fg-opacity, 0);
}
}
@keyframes mdc-ripple-fg-opacity-out {
from {
animation-timing-function: linear;
opacity: var(--mdc-ripple-fg-opacity, 0);
}
to {
opacity: 0;
}
}
.mdc-ripple-surface--test-edge-var-bug {
--mdc-ripple-surface-test-edge-var: 1px solid #000;
visibility: hidden;
}
.mdc-ripple-surface--test-edge-var-bug::before {
border: var(--mdc-ripple-surface-test-edge-var);
}
.mdc-fab {
--mdc-ripple-fg-size: 0;
--mdc-ripple-left: 0;
--mdc-ripple-top: 0;
--mdc-ripple-fg-scale: 1;
--mdc-ripple-fg-translate-end: 0;
--mdc-ripple-fg-translate-start: 0;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
.mdc-fab::before, .mdc-fab::after {
position: absolute;
border-radius: 50%;
opacity: 0;
pointer-events: none;
content: "";
}
.mdc-fab::before {
transition: opacity 15ms linear, background-color 15ms linear;
z-index: 1;
}
.mdc-fab.mdc-ripple-upgraded::before {
transform: scale(var(--mdc-ripple-fg-scale, 1));
}
.mdc-fab.mdc-ripple-upgraded::after {
top: 0;
/* @noflip */
left: 0;
transform: scale(0);
transform-origin: center center;
}
.mdc-fab.mdc-ripple-upgraded--unbounded::after {
top: var(--mdc-ripple-top, 0);
/* @noflip */
left: var(--mdc-ripple-left, 0);
}
.mdc-fab.mdc-ripple-upgraded--foreground-activation::after {
animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
}
.mdc-fab.mdc-ripple-upgraded--foreground-deactivation::after {
animation: mdc-ripple-fg-opacity-out 150ms;
transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
}
.mdc-fab::before, .mdc-fab::after {
top: calc(50% - 100%);
/* @noflip */
left: calc(50% - 100%);
width: 200%;
height: 200%;
}
.mdc-fab.mdc-ripple-upgraded::after {
width: var(--mdc-ripple-fg-size, 100%);
height: var(--mdc-ripple-fg-size, 100%);
}
.mdc-fab::before, .mdc-fab::after {
background-color: #fff;
}
@supports not (-ms-ime-align: auto) {
.mdc-fab::before, .mdc-fab::after {
/* @alternate */
background-color: var(--mdc-theme-on-secondary, #fff);
}
}
.mdc-fab:hover::before {
opacity: 0.08;
}
.mdc-fab:not(.mdc-ripple-upgraded):focus::before, .mdc-fab.mdc-ripple-upgraded--background-focused::before {
transition-duration: 75ms;
opacity: 0.24;
}
.mdc-fab:not(.mdc-ripple-upgraded)::after {
transition: opacity 150ms linear;
}
.mdc-fab:not(.mdc-ripple-upgraded):active::after {
transition-duration: 75ms;
opacity: 0.24;
}
.mdc-fab.mdc-ripple-upgraded {
--mdc-ripple-fg-opacity: 0.24;
}
</style>
<div class="horizontal-list-container">
{#if leftEnable}
<div class="mdc-fab mdc-fab--mini skip-button start" on:click="{moveLeft}"
transition:fly="{{ x: -100, duration: 200 }}" role="button">
<svg class="mdc-fab__icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M15.41 16.59L10.83 12l4.58-4.59L14 6l-6 6 6 6 1.41-1.41z"/>
<path fill="none" d="M0 0h24v24H0V0z"/>
</svg>
</div>
{/if}
<div class="items" bind:this={list}>
<slot></slot>
</div>
{#if leftRight}
<div class="mdc-fab mdc-fab--mini skip-button end" on:click="{moveRight}"
transition:fly="{{ x: 100, duration: 200 }}" role="button">
<svg class="mdc-fab__icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z"/>
<path fill="none" d="M0 0h24v24H0V0z"/>
</svg>
</div>
{/if}
</div>
<svelte:options tag="sv-scrollable"/>