<div id="wrapper">
  <div class="import-files">
    <div id="drop-area">
      <label for="file-selector">
        <div>
          <span>Drag'n </span>
          <span class="drop">drop your photos</span>
          <span>or</span>
        </div>
        <div>
          &nbsp;<span class="fake-link">browse...</span>
        </div>
          <input type="file" id="file-selector" multiple accept="image/*" />
        </div>
      </label>
    </div>
  </div>

<div id="processing">Processing...</div>

  <p id="view">
  </p>

  <template>
    <div class="photo" style="background-image:url('${item.thumbnailBase64}')" >
      <h3>${item.name}</h3>
      <div>
        ${item.size4human}
      </div>
      <div>
        <span>
          ${item.width}x${item.height}
        </span>
        &nbsp;
        <small>
          resized to ${item.widthReduced}x${item.heightReduced}
        </small>

      </div>
      <button type="button" onclick="this.parentNode.remove()">❌</button>
    </div>
  </template>

</div>
body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
  -webkit-font-smoothing: antialiased;
  background: black;
  height: 100vh;
  padding: 16px 16px 32px 16px;
}

#processing{
  display: none;
  color : white;
  text-align: center;
}

.import-files {
  background-color: white;
  padding: 16px;
  border-radius: 20px;
}

#drop-area {
  border-radius: 20px;
  padding: 0;
  transition: 0.3s;

  > label {
    cursor: pointer;
    border: 2px dashed grey;
    min-height: 64px;
    border-radius: 20px;
    padding: 12px;
    display: flex;
    flex-direction: column;
    font-size: 32px;
    position: relative;

    @media (min-width: 600px) {
      flex-direction: row;
      font-size: 48px;
    }

    //span {
    align-items: center;
    text-align: center;
    justify-content: center;
    transition: 0.3s;
    transform: scale(1);
    background-image: linear-gradient(
      90deg,
      #ffeb3b,
      #9c27b0 41.07%,
      #e91e63 76.05%,
      #ff7a18
    );
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    text-shadow: none;
    font-weight: bold;
    //}

    span {
      transition: 0;
    }

    .fake-link {
      color: blue;
    }

    &:hover,
    &:focus {
      border-color: blue;
      .fake-link {
        text-decoration: underline;
      }
    }

    input {
      display: none;
    }
  }

  &.drop-active {
    box-shadow: inset 0 0 16px grey;

    > label {
      //transform: scale(1.025);
      transition: 0.15s;
      border-style: solid;
    }

    span:not(.drop) {
      opacity: 0;
    }
  }
}

.mt-1 {
  margin-top: 1em;
}

.photo {
  background-size: cover;
  background-repeat: no-repeat;
  background-position: center;
  width: 300px;
  height: 200px;
  border-radius: 16px;
  display: inline-flex;
  flex-flow: column;
  justify-content: space-between;
  align-items: start;
  vertical-align: top;
  margin: 0 8px 8px 0;
  border: 2px solid gold;
  padding: 8px;
  box-sizing: border-box;
  position: relative;

  h3 {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    word-break: break-all;
  }

  * {
    background: rgb(255 255 255 / 50%);
    color: black;
    backdrop-filter: blur(5px);
    padding: 5px;
    border-radius: 16px;
    display: inline-block;
  }

  button {
    position: absolute;
    top: 0;
    right: 0;
  }
}
View Compiled
window.onload = function () {
  const fileSelector = document.getElementById("file-selector");
  const wrapper = document.body;
  const dropArea = document.getElementById("drop-area");
  //let timer = null;

  wrapper?.addEventListener("dragover", (event) => {
    event.stopPropagation();
    event.preventDefault();
    dropArea.classList.add("drop-active");
    // Style the drag-and-drop as a "copy file" operation.
    if (event && event.dataTransfer) {
      event.dataTransfer.dropEffect = "copy";
    }
  });

  wrapper?.addEventListener("dragleave", (event) => {
    //clearTimeout(timer);
    // timer = setTimeout(() => {
    dropArea.classList.remove("drop-active");
    // }, 1000);
  });

  wrapper?.addEventListener("drop", (event) => {
    event.stopPropagation();
    event.preventDefault();
    dropArea.classList.remove("drop-active");
    if (event && event.dataTransfer) {
      getFiles((event.dataTransfer.files as unknown) as File[]);
    }
  });

  if (window.FileList && window.File && fileSelector) {
    fileSelector.addEventListener("change", async (event: any) => {
      getFiles(event.target.files);
    });
  }
};

function formatFileSize(bytes: number = 0): string {
  const units = ["Octets", "Ko", "Mo", "Go", "To", "Po"];

  // https://stackoverflow.com/a/20732091
  const i = Math.floor(Math.log(bytes) / Math.log(1024));
  return (bytes / Math.pow(1024, i)).toFixed(2) * 1 + " " + units[i];
}

interface FileExtended extends File {
  thumbnailBase64: string;
  loading: boolean;
  width: number;
  height: number;

  widthReduced: number;
  heightReduced: number;
}

async function getFiles(files: File[]) {
  document.getElementById("processing").style.display = "block";

  for (const file of files) {
    if (file.type.startsWith("image")) {
      const item: FileExtended = file;

      item.size4human = formatFileSize(file.size);

      item.loading = true;
      const { resized, original }: any = await getThumbnailImg(item, 450, 300);
      item.thumbnailBase64 = resized;
      item.loading = false;
      item.original = original;

      const template = document.querySelector("template").innerHTML;
      document.querySelector("#view").innerHTML += eval("`" + template + "`");
    }
  }

  document.getElementById("processing").style.display = "none";
}

// To display a thumbnail
async function getThumbnailImg(
  file: FileExtended,
  maxWidth: number = 100,
  maxHeight: number = 100
): Promise<{ resized: string; original: string }> {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();

    reader.onload = function (e) {
      const img = document.createElement("img");
      img.src = e.target?.result as string;
      img.onload = function (e2) {
        const canvas = document.createElement("canvas");
        const ctx = canvas.getContext("2d");
        ctx?.drawImage(img, 0, 0);

        file.width = img.width;
        file.height = img.height;

        canvas.width = img.width;
        canvas.height = img.height;

        // calculate the new width / height respecting the original ratio
        if (img.width > img.height) {
          if (img.width > maxWidth) {
            canvas.height *= maxWidth / img.width;
            canvas.width = maxWidth;
          }
        } else {
          if (img.height > maxHeight) {
            canvas.width *= maxHeight / img.height;
            canvas.height = maxHeight;
          }
        }

        file.widthReduced = canvas.width;
        file.heightReduced = canvas.height;

        // resize
        const ctx2 = canvas.getContext("2d");
        ctx2?.drawImage(img, 0, 0, canvas.width, canvas.height);

        const dataurl = canvas.toDataURL(file.type);
        resolve({ resized: dataurl, original: e.target?.result });
      };

      img.onerror = (error) => {
        reject(error);
      };
    };
    reader.onerror = (error) => {
      reject(error);
    };
    reader.readAsDataURL(file);
  });
}
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.