Create Card Slider Using Python Tkinter

How To Create a Card Slider in Python Using Tkinter


How To Create a Card Slider in Python Using Tkinter


In this Python Tutorial we will see How to Create a Card Slider with smooth animations, drag functionality, and modern design Using Python and Tkinter library.
The slider can hold multiple cards, each with customizable content and unique colors.


What We Are Gonna Use In This Project:

- Python Programming Language.
- Tkinter (GUI).
- VS Editor.





Project Source Code:



import tkinter as tk
from datetime import datetime
import math

class CardSlider(tk.Canvas):
def __init__(self, master, **kwargs):
super().__init__(master, **kwargs)

# Basic card measurements
self.CARD_WIDTH = 340 # Width of each card
self.CARD_HEIGHT = 440 # Height of each card
self.CARD_SPACING = 40 # Space between cards
self.ANIMATION_DURATION = 400 # How long animations take (milliseconds)
self.CARD_PADDING = 25 # Space inside cards

# Colors for the slider
self.BACKGROUND_COLOR = "#0A0F1C" # Dark background
self.ACCENT_COLOR = "#4F46E5" # Modern indigo
self.CARD_BACKGROUND = "#151B2E" # Dark blue-gray
self.TEXT_COLOR = "#FFFFFF" # White
self.TEXT_SECONDARY = "#94A3B8" # Light gray
self.BUTTON_NORMAL = "#1F2937" # Dark gray
self.BUTTON_HOVER = "#374151" # Medium gray
self.BUTTON_PRESSED = "#4B5563" # Light gray
self.BORDER_COLOR = "#2D3748" # Border color

# Variables to track slider state
self.cards = [] # List to hold all card data
self.current_index = 0 # Which card is currently shown
self.slide_offset = 0 # Current horizontal offset for sliding animation
self.target_offset = 0 # Where the slide animation will end
self.start_offset = 0 # Where the slide animation started
self.animation_start = None # When the animation started
self.is_animating = False # Whether animation is in progress

# Mouse drag variables
self.is_dragging = False # Whether the user is dragging the cards
self.drag_start_x = 0 # X position where dragging started
self.drag_start_offset = 0 # Card offset when dragging started
self.drag_velocity = 0 # Speed of the drag for momentum effect
self.last_drag_x = 0 # Last X position during dragging
self.last_drag_time = None # Time of last drag event for velocity calculation

# Set up the canvas appearance
self.configure(bg=self.BACKGROUND_COLOR, highlightthickness=0)

# Connect event handlers
self.bind("<Configure>", self.on_resize) # Handle window resizing

# Setup mouse drag events
# When mouse button is pressed
self.bind("<ButtonPress-1>", self.on_drag_start)
# When mouse is moved while button is pressed
self.bind("<B1-Motion>", self.on_drag_move)
# When mouse button is released
self.bind("<ButtonRelease-1>", self.on_drag_end)

# Create left/right navigation buttons
self.create_navigation_buttons()

# Start animation loop
self.animate()


def on_drag_start(self, event):
"""Start dragging when mouse button is pressed"""
# Don't start drag if clicking on a button
if self.is_clicking_button(event.x, event.y):
return
self.is_dragging = True
self.is_animating = False # Stop any current animation
self.drag_start_x = event.x
self.drag_start_offset = self.slide_offset
self.last_drag_x = event.x
self.last_drag_time = datetime.now()

def is_clicking_button(self, x, y):
"""Check if the mouse click is on one of the navigation buttons"""
# Calculate button positions
center_y = (self.winfo_height() - self.CARD_HEIGHT) // 2
button_y = center_y + (self.CARD_HEIGHT - 50) // 2

# Check if click is within either button
if (x < 65 and abs(y - button_y) < 25): # Left button
return True
if (x > self.winfo_width() - 65 and abs(y - button_y) < 25): # Right button
return True

return False



def on_drag_move(self, event):
"""Handle mouse movement during dragging"""
if not self.is_dragging:
return
# Calculate how far we've dragged
drag_distance = event.x - self.drag_start_x
self.slide_offset = self.drag_start_offset + drag_distance
# Calculate speed for momentum effect
now = datetime.now()
time_diff = (now - self.last_drag_time).total_seconds()
if time_diff > 0:
distance = event.x - self.last_drag_x
self.drag_velocity = distance / time_diff # Speed = distance/time
self.last_drag_x = event.x
self.last_drag_time = now
# Limit how far user can drag beyond the edge cards
min_offset = -(len(self.cards) - 1) * (self.CARD_WIDTH + self.CARD_SPACING)
resistance_factor = 0.3 # Makes it harder to drag beyond edges

# Apply resistance when dragging past the first card
if self.slide_offset > 0:
self.slide_offset = self.slide_offset * resistance_factor
# Apply resistance when dragging past the last card
elif self.slide_offset < min_offset:
overshoot = self.slide_offset - min_offset
self.slide_offset = min_offset + (overshoot * resistance_factor)
self.redraw()


def on_drag_end(self, event):
"""Handle mouse release to end dragging"""
if not self.is_dragging:
return

self.is_dragging = False

# Figure out which card should be shown based on current position
card_width_with_spacing = self.CARD_WIDTH + self.CARD_SPACING
# Add momentum effect - continue in direction of drag with decreasing velocity
momentum_factor = 0.3 # How much momentum to apply
momentum_distance = self.drag_velocity * momentum_factor

# Calculate position after momentum
momentum_offset = self.slide_offset + momentum_distance

# Calculate which card we'd land on
rough_index = -momentum_offset / card_width_with_spacing

# If moving slowly, snap to nearest card
# If moving quickly, go to next card in the direction of movement
if abs(self.drag_velocity) < 500:
self.current_index = round(rough_index)
else:
if self.drag_velocity > 0: # Moving right (towards earlier cards)
self.current_index = math.floor(rough_index)
else: # Moving left (towards later cards)
self.current_index = math.ceil(rough_index)

# Make sure index is within bounds
# (not less than 0 or greater than number of cards)
self.current_index = max(0, min(len(self.cards) - 1, self.current_index))

# Start animation to selected card
self.start_slide_animation()



def create_navigation_buttons(self):
"""Create the left and right arrow buttons"""
class ModernButton(tk.Canvas):
def __init__(self, master, command, icon, **kwargs):
# Create a circular button with an arrow icon
super().__init__(master, width=50, height=50, highlightthickness=0,
**kwargs)
self.command = command # Function to call when clicked
self.configure(bg=self.master.BACKGROUND_COLOR)

# Button colors for different states
self.normal_color = self.master.BUTTON_NORMAL
self.hover_color = self.master.BUTTON_HOVER
self.pressed_color = self.master.BUTTON_PRESSED

# Connect mouse events
self.bind('<Enter>', self.on_enter) # Mouse hovering
self.bind('<Leave>', self.on_leave) # Mouse leaving
self.bind('<Button-1>', self.on_press) # Mouse button pressed
self.bind('<ButtonRelease-1>', self.on_release) #Mouse button released

self.current_color = self.normal_color
self.icon = icon # Arrow symbol (← or →)
self.draw()

def draw(self):
"""Draw the button with current styling"""
self.delete("all") # Clear previous drawing

# Create button
radius = 20
center_x, center_y = 25, 25

# Main button body
self.create_rectangle(
center_x - radius,
center_y - radius,
center_x + radius,
center_y + radius,
fill=self.current_color,
outline="yellow"
)

# Draw arrow icon with slight offset for pressed state
text_color = self.master.TEXT_COLOR
text_x, text_y = center_x, center_y
if self.current_color == self.pressed_color:
text_x += 1
text_y += 1

self.create_text(
text_x,
text_y,
text=self.icon,
fill=text_color,
font=("Segoe UI", 16, "bold")
)

def on_enter(self, e):
"""Change color when mouse enters button area"""
self.current_color = self.hover_color
self.draw()

def on_leave(self, e):
"""Change color when mouse leaves button area"""
self.current_color = self.normal_color
self.draw()

def on_press(self, e):
"""Change color when button is pressed"""
self.current_color = self.pressed_color
self.draw()

def on_release(self, e):
"""Execute command when button is released"""
# Only trigger if mouse is still over button
if 0 <= e.x <= 50 and 0 <= e.y <= 50:
self.command() # Execute the button's action
self.current_color = self.hover_color \
if 0 <= e.x <= 50 and 0 <= e.y <= 50 \
else self.normal_color
self.draw()


# Create the previous and next buttons
self.prev_button = ModernButton(self, self.previous_card, "←") # Left arrow
self.next_button = ModernButton(self, self.next_card, "→") # Right arrow

# Placeholders for button positions (will be set in redraw)
self.prev_button_window = None
self.next_button_window = None




def wrap_text(self, canvas, x, y, text, font, max_width, anchor="w", fill=""):
"""Break text into multiple lines that fit within max_width"""
words = text.split()
line = ""
y_text = y
line_height = self.tk.call("font", "metrics", font, "-linespace")

for word in words:
# Try adding the next word to the current line
test_line = line + " " + word if line else word
width = self.tk.call("font", "measure", font, test_line)

if width <= max_width: # Word fits on current line
line = test_line
else: # Word doesn't fit, start a new line
canvas.create_text(x, y_text, text=line, font=font,
anchor=anchor, fill=fill)
y_text += line_height
line = word
# Draw the last line if there's text remaining
if line:
canvas.create_text(x, y_text, text=line, font=font,
anchor=anchor, fill=fill)
y_text += line_height

return y_text - y # Return the height used



def draw_card(self, card, x, y, scale=1.0, alpha=1.0):
"""Draw a single card at the specified position with scaling effects"""
# Calculate card dimensions with scale
scaled_width = self.CARD_WIDTH * scale
scaled_height = self.CARD_HEIGHT * scale

# Center the card at its position
x1 = x + (self.CARD_WIDTH - scaled_width) / 2
y1 = y + (self.CARD_HEIGHT - scaled_height) / 2
x2 = x1 + scaled_width
y2 = y1 + scaled_height

# Draw card background (the main rectangle)
self.create_rectangle(x1, y1, x2, y2,
fill=self.CARD_BACKGROUND,
outline=self.BORDER_COLOR)

# Add colored strip at top of card
self.create_rectangle(x1, y1, x2, y1 + 6,
fill=card["accent_color"],
outline="")
# Calculate available space for content
content_width = scaled_width - (self.CARD_PADDING * 2 * scale)
content_x = x1 + (self.CARD_PADDING * scale)
content_y = y1 + (40 * scale) # Start below the accent strip

# Draw card title with word wrapping
title_font = ("Segoe UI", int(28 * scale), "bold")
content_y += self.wrap_text(
self, content_x, content_y,
card["title"], title_font,
content_width, "w", self.TEXT_COLOR
)

# Add spacing between title and description
content_y += 20 * scale

# Draw card description with word wrapping
desc_font = ("Segoe UI", int(16 * scale))
self.wrap_text(
self, content_x, content_y,
card["description"], desc_font,
content_width, "w", self.TEXT_SECONDARY
)

def draw_indicators(self):
"""Draw the dots at bottom that show which card is selected"""
if not self.cards:
return

DOT_SIZE = 6 # Size of each indicator dot
DOT_SPACING = 8 # Space between dots
total_width = len(self.cards) * (DOT_SIZE + DOT_SPACING) - DOT_SPACING

# Center the indicator dots
start_x = (self.winfo_width() - total_width) // 2
y = self.winfo_height() - 70 # Position near bottom

# Draw indicator background
bg_padding = 10
bg_x1 = start_x - bg_padding
bg_x2 = start_x + total_width + bg_padding
bg_y1 = y - bg_padding
bg_y2 = y + DOT_SIZE + bg_padding

# Create rectangle for dot container
self.create_rectangle(bg_x1, bg_y1, bg_x2, bg_y2,
fill=self.CARD_BACKGROUND,
outline=self.BORDER_COLOR)

# Draw a dot for each card
for i in range(len(self.cards)):
x = start_x + i * (DOT_SIZE + DOT_SPACING)

if i == self.current_index:
# Current card gets highlighted dot
self.create_rectangle(x, y, x + DOT_SIZE, y + DOT_SIZE,
fill="orange", outline="")
else:
# Other cards get normal dots
self.create_rectangle(x, y, x + DOT_SIZE, y + DOT_SIZE,
fill=self.BUTTON_NORMAL, outline="")



def add_card(self, title, description, accent_color):
"""Add a new card to the slider"""
card = {
"title": title,
"description": description,
"accent_color": accent_color
}

self.cards.append(card)
self.redraw()


def previous_card(self):
"""Move to previous card if possible"""
if self.current_index > 0:
self.current_index -= 1 # Move to previous card
self.start_slide_animation()

def next_card(self):
"""Move to next card if possible"""
if self.current_index < len(self.cards) - 1:
self.current_index += 1 # Move to next card
self.start_slide_animation()

def start_slide_animation(self):
"""Begin animation to slide to the targeted card"""
self.start_offset = self.slide_offset # Where we're starting from
# Calculate target position (negative index * width of card including spacing)
self.target_offset = -self.current_index * (self.CARD_WIDTH + self.CARD_SPACING)
self.animation_start = datetime.now() # Record when animation started
self.is_animating = True # Flag that we're animating


def animate(self):
"""Animation loop that runs continuously to create smooth movement"""
if self.is_animating and self.animation_start:
# Calculate how far along the animation is (0.0 to 1.0)
elapsed = (datetime.now() - self.animation_start).total_seconds() * 1000
progress = min(1.0, elapsed / self.ANIMATION_DURATION)

# Smooth out the animation using cosine easing
# This makes the animation start slow, speed up, then slow down at the end
progress = 1 - (math.cos(progress * math.pi) + 1) / 2

# Calculate current position
self.slide_offset = (self.start_offset +
(self.target_offset - self.start_offset) * progress)

# Check if animation is complete
if progress >= 1:
self.slide_offset = self.target_offset
self.is_animating = False

self.redraw() # Redraw with new position

# Schedule next animation frame
self.after(16, self.animate)


def on_resize(self, event):
"""Handle window resize events"""
self.redraw() # Redraw everything when window size changes


def redraw(self):
"""Clear and redraw everything on the canvas"""
self.delete("all") # Clear everything

if not self.cards:
return # Nothing to draw if no cards

# Find center of the window
center_x = (self.winfo_width() - self.CARD_WIDTH) // 2
center_y = (self.winfo_height() - self.CARD_HEIGHT) // 2

# Draw all visible cards
for i, card in enumerate(self.cards):
# Calculate position of this card
x = (center_x + int(i * (self.CARD_WIDTH + self.CARD_SPACING) +
self.slide_offset))

# Only draw cards that are visible on screen (for better performance)
if x + self.CARD_WIDTH >= 0 and x <= self.winfo_width():
# Calculate how far the card is from center
distance = abs(x - center_x)

# Scale and fade cards based on distance from center
# Cards in center are larger and more visible
scale = max(0.85, 1.0 - distance / (self.winfo_width() * 0.8))
alpha = max(0.6, 1.0 - distance / (self.winfo_width() * 0.8))

self.draw_card(card, x, center_y, scale, alpha)

# Draw the indicator dots
self.draw_indicators()

# Position navigation buttons
button_y = center_y + (self.CARD_HEIGHT - 50) // 2

# Remove old button positions
if self.prev_button_window:
self.delete(self.prev_button_window)
if self.next_button_window:
self.delete(self.next_button_window)

# Add buttons at new positions
self.prev_button_window = self.create_window(40,
button_y, window=self.prev_button)
self.next_button_window = self.create_window(
self.winfo_width() - 40, button_y, window=self.next_button)

# Update cursor based on whether we're dragging
if self.is_dragging:
self.config(cursor="fleur")
else:
self.config(cursor="")




def main():
"""Main function to run the application"""
# Create main window
root = tk.Tk()
root.title("Modern Card Slider")
root.geometry("1200x700") # Set starting window size
root.configure(bg="#0A0F1C") # Set background color

# Create the slider and make it fill the window
slider = CardSlider(root)
slider.pack(fill=tk.BOTH, expand=True, padx=20, pady=20)

# Add keyboard navigation
root.bind("<Left>", lambda event: slider.previous_card()) # Left arrow key
root.bind("<Right>", lambda event: slider.next_card()) # Right arrow key

# Add example cards with different colors
slider.add_card(
"Clean Code Practices",
"Write maintainable, readable code with proper documentation and consistent " +
"formatting. Essential for collaborative development.",
"#4F46E5" # Indigo
)

slider.add_card(
"Efficient Algorithms",
"Optimize performance with well-designed algorithms and data structures. " +
"Reduce complexity and improve execution speed.",
"#7C3AED" # Purple
)

slider.add_card(
"API Integration",
"Connect your application to external services with RESTful or GraphQL APIs " +
"for enhanced functionality.",
"#EC4899" # Pink
)

slider.add_card(
"Test-Driven Development",
"Write tests before implementing features to ensure reliability " +
"and catch bugs early in the development cycle.",
"#14B8A6" # Teal
)

slider.add_card(
"Responsive Web Design",
"Create applications that work seamlessly across all device sizes " +
"using modern CSS frameworks and media queries.",
"#F59E0B" # Amber
)

slider.add_card(
"Performance Optimization",
"Implement efficient memory management, lazy loading, " +
"and code splitting to deliver fast, responsive applications.",
"#10B981" # Emerald
)

slider.add_card(
"Version Control",
"Track code changes, collaborate effectively, " +
"and maintain project history with Git and other VCS tools.",
"#6366F1" # Indigo
)

slider.add_card(
"Security Best Practices",
"Protect your applications from common vulnerabilities with input validation, " +
"authentication, and data encryption.",
"#EF4444" # Red
)

slider.add_card(
"Containerization",
"Package applications with dependencies using Docker " +
"to ensure consistency across development, testing, and production environments.",
"#0EA5E9" # Sky Blue
)

slider.add_card(
"CI/CD Pipelines",
"Automate testing and deployment processes " +
"to deliver code changes more frequently and reliably to end users.",
"#8B5CF6" # Violet
)

# Start the application
root.mainloop()



if __name__ == "__main__":
main()




The Final Result:

Create Card Slider Using Python Tkinter - 1

Create Card Slider Using Python Tkinter - 2

Create Card Slider Using Python Tkinter - 3



if you want the full source code click on the download button below




disclaimer: you will get the source code, and to make it work in your machine is your responsibility and to debug any error/exception is your responsibility this project is for the students who want to see an example and read the code not to get and run.






Share this

Related Posts

Latest
Previous
Next Post »