body {
  width: 100%;
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
}

.download-link {
  font-size: 1.25rem;
  font-weight: bold;
  padding: 1rem 2.5rem;
}
(() => {
  
  function downloadBlob(blob, filename) {
    // Create an object URL for the blob object
    const url = URL.createObjectURL(blob);

    // Create a new anchor element
    const a = document.createElement('a');

    // Set the href and download attributes for the anchor element
    // You can optionally set other attributes like `title`, etc
    // Especially, if the anchor element will be attached to the DOM
    a.href = url;
    a.download = filename || 'download';

    // Click handler that releases the object URL after the element has been clicked
    // This is required for one-off downloads of the blob content
    const clickHandler = function() {
      setTimeout(() => {
        // Release the object URL
        URL.revokeObjectURL(url);
        
        // Remove the event listener from the anchor element
        this.removeEventListener('click', clickHandler);
        
        // Remove the anchor element from the DOM
        (this.remove && (this.remove(), 1)) ||
        (this.parentNode && this.parentNode.removeChild(this));
      }, 150);
    };

    // Add the click event listener on the anchor element
    // a.addEventListener('click', clickHandler, false);

    // Programmatically trigger a click on the anchor element
    // Useful if you want the download to happen automatically
    // Without attaching the anchor element to the DOM
    // a.click();

    // Return the anchor element
    // Useful if you want a reference to the element
    // in order to attach it to the DOM or use it in some other way
    return a;
  }
  
  const square = ({ width = 1, height = width } = {}) => (width / height) === 1;
  
  const smallerThan = size => {
    return (Number.isFinite(size) && size >= 1)
      ? ({ width = 1, height = width } = {}) => Math.max(width, height) <= size
      : () => false;
  }
  
  const largerThan = size => {
    return (Number.isFinite(size) && size >= 1)
      ? ({ width = 1, height = width } = {}) => Math.min(width, height) >= size
      : () => false;
  }
  
  const format = type => {
    return (['jpeg', 'png'].includes(type))
      ? ({ format } = {}) => format === type
      : () => false;
  }
  
  const compose = (...fns) => (...args) => {
    let bool = true;
    
    for (let fn of fns.reverse()) {
      if (!bool) break;
      bool = bool && (typeof fn === 'function') && !!fn(...args);
    }
    
    return bool;
  }
  
  const collectionToCSV = (keys = []) => (collection = []) => {
    const headers = keys.map(key => {
      const [ access, output = access ] = [].concat(key);
      return `"${ (typeof output === 'function') ? access : output }"`;
    }).join(',');
    
    const values = record => keys.map(key => {
      const [ access, output = access, transform = output ] = [].concat(key);
      
      const [ ...values ] = Array.isArray(access)
        ? access.map(key => record[key] || null)
        : [ record[access] || null ];
        
      return `"${
        (typeof transform === 'function')
          ? transform(...values) || ''
          : values.filter(v => v !== null).join(' ')
      }"`;
    }).join(',');
    
    return collection.reduce((csv, record) => {
      return (`${csv}\n${values(record)}`).trim();
    }, headers);
  };
  
  const sizeTransform = (...dim) => dim.slice(0, 2).join(' x ');
  
  const orientationTransform = (w, h, o = w / h) => (
    o > 1 ? 'landscape' : o < 1 ? 'portrait' : 'square'
  ).toUpperCase();
  
  const csvExports = [
    'id',
    'author',
    'filename',
    [ 'format', format => format.toUpperCase() ],
    [ ['width', 'height'], 'size', sizeTransform ],
    [ ['width', 'height'], 'orientation', orientationTransform ]
  ];
  
  const filters = compose(
    largerThan(1500),
    smallerThan(2500),
    square,
    format('jpeg')
  );
  
  fetch('https://picsum.photos/list')
    .then(response => response.json())
    .then(data => data.filter(filters))
    .then(collectionToCSV(csvExports))
    .then(csv => {
      console.log(csv);
      const blob = new Blob([csv], { type: 'text/csv' });
    
      const downloadLink = downloadBlob(blob, 'photos.csv');
      
      // Set the title and classnames of the link
      downloadLink.title = 'Export Photos as CSV';
      downloadLink.classList.add('btn', 'btn-primary', 'download-link');

      // Set the text content of the download link
      downloadLink.textContent = 'Export Photos';

      // Attach the link to the DOM
      document.body.appendChild(downloadLink);
    })
    .catch(console.error);
})();


// function squareImages({ width = 1, height = width } = {}) {
//   return width / height === 1;
// }

// function collectionToCSV(keys = []) {
//   return (collection = []) => {
//     const headers = keys.map(key => `"${key}"`).join(',');
//     const extractKeyValues = record => keys.map(key => `"${record[key]}"`).join(',');

//     return collection.reduce((csv, record) => {
//       return (`${csv}\n${extractKeyValues(record)}`).trim();
//     }, headers);
//   }
// }

// const exportFields = [ 'id', 'author', 'filename', 'format', 'width', 'height' ];

// fetch('https://picsum.photos/list')
//   .then(response => response.json())
//   .then(data => data.filter(squareImages))
//   .then(collectionToCSV(exportFields))
//   .then(csv => {
//     const blob = new Blob([csv], { type: 'text/csv' });
//     downloadBlob(blob, 'photos.csv');
//   })
//   .catch(console.error);

External CSS

  1. https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.min.css

External JavaScript

This Pen doesn't use any external JavaScript resources.