Skip to main content

Responsive Card Images

Learn how to implement responsive card images using the Card Images API's automatic thumbnail generation system. This guide covers modern responsive image techniques including srcset, sizes, the <picture> element, and lazy loading for optimal performance across all devices.

Overview

The Card Images API automatically generates three optimized thumbnail variants for every uploaded image, enabling you to deliver the perfect image size for each device and viewport. This ensures:

  • Fast page loads - Smaller images for mobile devices
  • Sharp displays - High-resolution images for retina screens
  • Bandwidth savings - Users only download what they need
  • Better UX - Instant loading with lazy loading techniques

Automatic Thumbnail Generation

When you upload a card image, the API automatically creates three thumbnail variants in addition to the original:

VariantWidthQualityUse Case
small150px75%Mobile lists, thumbnails, low-bandwidth
medium300px80%Tablet views, standard displays
large600px85%Desktop views, retina mobile
originalAs uploaded100%Full-screen, zoom, high-res displays

Key Features

  • Async generation - Thumbnails are created in the background via queued jobs
  • Aspect ratio preserved - All variants maintain the original aspect ratio
  • JPEG format - All images are converted to optimized JPEG
  • CDN ready - URLs redirect to CDN for fast global delivery
Async Generation

Thumbnails are generated asynchronously. If a variant isn't ready yet, the download endpoint returns the original image. For production apps, consider polling or showing a loading state.

Requesting Image Sizes

Use the size parameter on the download endpoint to request specific variants:

# Get small thumbnail (150px)
GET /v1/card-images/{id}/download?size=small

# Get medium thumbnail (300px)
GET /v1/card-images/{id}/download?size=medium

# Get large thumbnail (600px)
GET /v1/card-images/{id}/download?size=large

# Get original image (no size parameter)
GET /v1/card-images/{id}/download

Versioned CDN URLs

All card images are delivered via DigitalOcean Spaces CDN with automatic versioned URLs for cache invalidation. When you fetch image metadata, the API returns CDN URLs with version parameters:

{
"data": {
"attributes": {
"download_url": "https://cdn.tradingcardapi.com/cards/.../image.jpg?v=1699564800",
"variants": {
"small": {
"url": "https://cdn.tradingcardapi.com/cards/.../image_small.jpg?v=1699564800"
}
}
}
}
}

The ?v=1699564800 version parameter is automatically updated when the image changes, ensuring browsers and CDN always serve the latest version without manual cache purging. See the CDN Integration guide for details.

Use Metadata URLs

Instead of constructing download endpoint URLs manually, fetch image metadata and use the provided CDN URLs. These include the version parameter and point directly to CDN edge locations for optimal performance.

Basic Responsive Images with srcset

The srcset attribute tells the browser which images are available, letting it choose the best one based on screen size and pixel density.

Pixel Density Descriptors (1x, 2x, 3x)

Use for fixed-size images where only screen pixel density varies:

<img
src="https://api.tradingcardapi.com/v1/card-images/abc123/download?size=small"
srcset="
https://api.tradingcardapi.com/v1/card-images/abc123/download?size=small 1x,
https://api.tradingcardapi.com/v1/card-images/abc123/download?size=medium 2x,
https://api.tradingcardapi.com/v1/card-images/abc123/download?size=large 3x
"
alt="1989 Upper Deck Ken Griffey Jr. #1"
loading="lazy"
/>

When to use: Fixed-width thumbnails in card lists or grids.

Width Descriptors (w)

Use when the image size changes based on viewport width:

<img
src="https://api.tradingcardapi.com/v1/card-images/abc123/download?size=small"
srcset="
https://api.tradingcardapi.com/v1/card-images/abc123/download?size=small 150w,
https://api.tradingcardapi.com/v1/card-images/abc123/download?size=medium 300w,
https://api.tradingcardapi.com/v1/card-images/abc123/download?size=large 600w,
https://api.tradingcardapi.com/v1/card-images/abc123/download 1200w
"
sizes="(max-width: 640px) 150px,
(max-width: 1024px) 300px,
600px"
alt="1989 Upper Deck Ken Griffey Jr. #1"
loading="lazy"
/>

When to use: Responsive layouts where image width changes with viewport.

The sizes Attribute

The sizes attribute tells the browser how wide the image will be displayed at different viewport widths.

Example: Card Grid Layout

<!--
Mobile (< 640px): Full width = 100vw
Tablet (640px - 1024px): 2 columns = 50vw each
Desktop (> 1024px): 4 columns = 25vw each
-->
<img
src="https://api.tradingcardapi.com/v1/card-images/abc123/download?size=small"
srcset="
https://api.tradingcardapi.com/v1/card-images/abc123/download?size=small 150w,
https://api.tradingcardapi.com/v1/card-images/abc123/download?size=medium 300w,
https://api.tradingcardapi.com/v1/card-images/abc123/download?size=large 600w
"
sizes="(max-width: 640px) 100vw,
(max-width: 1024px) 50vw,
25vw"
alt="Card image"
loading="lazy"
/>

Example: Fixed-Width Sidebar

<!-- Sidebar is always 300px wide -->
<img
src="https://api.tradingcardapi.com/v1/card-images/abc123/download?size=small"
srcset="
https://api.tradingcardapi.com/v1/card-images/abc123/download?size=small 150w,
https://api.tradingcardapi.com/v1/card-images/abc123/download?size=medium 300w,
https://api.tradingcardapi.com/v1/card-images/abc123/download?size=large 600w
"
sizes="300px"
alt="Card image"
loading="lazy"
/>

The Picture Element

Use the <picture> element for art direction - when you want different images at different viewport sizes (not just scaled versions).

Example: Different Crops for Mobile

<picture>
<!-- Mobile: Show cropped front of card only -->
<source
media="(max-width: 640px)"
srcset="https://api.tradingcardapi.com/v1/card-images/front123/download?size=medium"
/>

<!-- Desktop: Show full card with both sides -->
<source
media="(min-width: 641px)"
srcset="
https://api.tradingcardapi.com/v1/card-images/front123/download?size=large,
https://api.tradingcardapi.com/v1/card-images/back123/download?size=large
"
/>

<!-- Fallback -->
<img
src="https://api.tradingcardapi.com/v1/card-images/front123/download?size=medium"
alt="1989 Upper Deck Ken Griffey Jr."
loading="lazy"
/>
</picture>

Lazy Loading

Lazy loading defers image loading until they're about to enter the viewport, drastically improving initial page load times.

Native Lazy Loading

Modern browsers support native lazy loading:

<img
src="https://api.tradingcardapi.com/v1/card-images/abc123/download?size=small"
srcset="
https://api.tradingcardapi.com/v1/card-images/abc123/download?size=small 150w,
https://api.tradingcardapi.com/v1/card-images/abc123/download?size=medium 300w,
https://api.tradingcardapi.com/v1/card-images/abc123/download?size=large 600w
"
sizes="(max-width: 640px) 150px, 300px"
alt="Card image"
loading="lazy"
/>

Browser support: All modern browsers (95%+ global support)

Intersection Observer (Advanced)

For more control, use the Intersection Observer API:

// Create observer
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;

// Load the image
img.src = img.dataset.src;
if (img.dataset.srcset) {
img.srcset = img.dataset.srcset;
}

// Remove placeholder class
img.classList.remove('lazy-loading');
img.classList.add('lazy-loaded');

// Stop observing this image
observer.unobserve(img);
}
});
}, {
rootMargin: '50px' // Start loading 50px before entering viewport
});

// Observe all lazy images
document.querySelectorAll('img.lazy').forEach(img => {
imageObserver.observe(img);
});

HTML:

<img
class="lazy lazy-loading"
data-src="https://api.tradingcardapi.com/v1/card-images/abc123/download?size=medium"
data-srcset="
https://api.tradingcardapi.com/v1/card-images/abc123/download?size=small 150w,
https://api.tradingcardapi.com/v1/card-images/abc123/download?size=medium 300w
"
alt="Card image"
/>

Framework Examples

React / Next.js

Next.js includes an optimized Image component:

import Image from 'next/image';

function CardImage({ imageId, alt }) {
const imageLoader = ({ src, width }) => {
// Map Next.js width to API size parameter
const sizeMap = {
150: 'small',
300: 'medium',
600: 'large'
};
const size = sizeMap[width] || 'small';
return `https://api.tradingcardapi.com/v1/card-images/${imageId}/download?size=${size}`;
};

return (
<Image
loader={imageLoader}
src={imageId}
alt={alt}
width={300}
height={400}
sizes="(max-width: 640px) 150px, 300px"
loading="lazy"
/>
);
}

Vue.js

<template>
<img
:src="getImageUrl(imageId, 'small')"
:srcset="generateSrcset(imageId)"
:sizes="sizes"
:alt="alt"
loading="lazy"
/>
</template>

<script>
export default {
props: {
imageId: String,
alt: String,
sizes: {
type: String,
default: '(max-width: 640px) 150px, 300px'
}
},
methods: {
getImageUrl(id, size) {
return `https://api.tradingcardapi.com/v1/card-images/${id}/download?size=${size}`;
},
generateSrcset(id) {
return `
${this.getImageUrl(id, 'small')} 150w,
${this.getImageUrl(id, 'medium')} 300w,
${this.getImageUrl(id, 'large')} 600w
`.trim();
}
}
};
</script>

Vanilla JavaScript

class CardImageRenderer {
constructor(imageId) {
this.imageId = imageId;
this.baseUrl = 'https://api.tradingcardapi.com/v1/card-images';
}

getImageUrl(size) {
const sizeParam = size ? `?size=${size}` : '';
return `${this.baseUrl}/${this.imageId}/download${sizeParam}`;
}

generateSrcset() {
return `
${this.getImageUrl('small')} 150w,
${this.getImageUrl('medium')} 300w,
${this.getImageUrl('large')} 600w,
${this.getImageUrl()} 1200w
`.trim();
}

render(alt, sizes = '(max-width: 640px) 150px, 300px') {
return `
<img
src="${this.getImageUrl('small')}"
srcset="${this.generateSrcset()}"
sizes="${sizes}"
alt="${alt}"
loading="lazy"
/>
`;
}
}

// Usage
const renderer = new CardImageRenderer('abc123');
document.getElementById('card-container').innerHTML = renderer.render(
'1989 Upper Deck Ken Griffey Jr. #1',
'(max-width: 640px) 100vw, 300px'
);

Performance Best Practices

1. Choose the Right Variant

Use CaseRecommended SizeReasoning
Mobile list thumbnailssmall (150px)Fast loading on cellular
Tablet/desktop listsmedium (300px)Good balance for standard displays
Detail view (mobile)large (600px)Sharp on retina mobile screens
Detail view (desktop)originalFull quality for zoom/inspection
Print/exportoriginalMaximum quality preserved

2. Use Appropriate Formats

All Card Images API variants are JPEG format with quality optimization:

  • Small: 75% quality - Optimized for thumbnails
  • Medium: 80% quality - Balanced quality/size
  • Large: 85% quality - High quality for larger displays

3. Implement Lazy Loading

Always use loading="lazy" for images below the fold:

<!-- Above the fold: eager loading -->
<img src="..." alt="..." loading="eager" />

<!-- Below the fold: lazy loading -->
<img src="..." alt="..." loading="lazy" />

4. Preload Critical Images

For hero images or featured cards, use <link rel="preload">:

<head>
<link
rel="preload"
as="image"
href="https://api.tradingcardapi.com/v1/card-images/abc123/download?size=large"
imagesrcset="
https://api.tradingcardapi.com/v1/card-images/abc123/download?size=small 150w,
https://api.tradingcardapi.com/v1/card-images/abc123/download?size=medium 300w,
https://api.tradingcardapi.com/v1/card-images/abc123/download?size=large 600w
"
imagesizes="(max-width: 640px) 150px, 300px"
/>
</head>

5. Cache Images Locally

For frequently accessed images, implement client-side caching:

class ImageCache {
constructor() {
this.cache = new Map();
}

async get(imageId, size) {
const key = `${imageId}-${size}`;

if (this.cache.has(key)) {
return this.cache.get(key);
}

const url = `https://api.tradingcardapi.com/v1/card-images/${imageId}/download?size=${size}`;
const response = await fetch(url);
const blob = await response.blob();
const objectUrl = URL.createObjectURL(blob);

this.cache.set(key, objectUrl);
return objectUrl;
}
}

Mobile Considerations

Bandwidth Awareness

Use the Network Information API to adapt image quality based on connection:

function getOptimalSize() {
// Check if Network Information API is available
if ('connection' in navigator) {
const connection = navigator.connection;

// Slow connections: use small images
if (connection.effectiveType === 'slow-2g' || connection.effectiveType === '2g') {
return 'small';
}

// Medium connections: use medium images
if (connection.effectiveType === '3g') {
return 'medium';
}
}

// Fast connections or unknown: use large images
return 'large';
}

// Usage
const optimalSize = getOptimalSize();
const imageUrl = `https://api.tradingcardapi.com/v1/card-images/abc123/download?size=${optimalSize}`;

Data Saver Mode

Respect user preferences for reduced data usage:

function shouldLoadHighQuality() {
if ('connection' in navigator && 'saveData' in navigator.connection) {
return !navigator.connection.saveData;
}
return true; // Default to high quality if unknown
}

const size = shouldLoadHighQuality() ? 'large' : 'small';

Troubleshooting

Thumbnails Not Available

Problem: Requesting a thumbnail returns the original image

Solutions:

  • Wait a few seconds - thumbnails are generated asynchronously
  • Poll the card-images endpoint to check if variants are ready
  • Show a loading state while thumbnails generate
  • Fall back to original if thumbnails aren't critical

Images Not Loading

Problem: Images fail to load or show broken image icon

Solutions:

  • Verify image ID is correct
  • Check authentication token is valid
  • Ensure image hasn't been soft-deleted
  • Check network console for error responses

Poor Performance

Problem: Images load slowly or consume too much bandwidth

Solutions:

  • Always use loading="lazy" for below-fold images
  • Use smaller variants for mobile devices
  • Implement srcset for responsive delivery
  • Enable CDN caching with appropriate headers
  • Preload only critical above-fold images

srcset Not Working

Problem: Browser always loads the same image regardless of screen size

Solutions:

  • Check that width descriptors match actual image widths
  • Verify sizes attribute accurately reflects layout
  • Test in different browsers (srcset support is universal in modern browsers)
  • Clear browser cache and hard reload

Next Steps