Lightweight Date Picker Component: From Sudoku100 Project Practice
Project Background
This date picker component was originally developed for my Sudoku100 project — a free, online sudoku game platform for sudoku enthusiasts of all skill levels. If you're interested in sudoku or looking for a fun online puzzle game, feel free to check it out at: https://www.sudoku100.com
During the development of Sudoku100, I discovered the need for a lightweight, easy-to-use, and multi-language date picker to handle features like user birthdays and game record timestamps. After researching existing solutions, I found that they were either too bloated or not flexible enough, so I decided to implement one myself.
Core Technical Points
1. iOS-Style Wheel Picker
This is the core interaction method of the component. By mimicking the iOS system's wheel picker, it provides users with a smooth and intuitive date selection experience.
Technical Implementation:
- Use CSS
overflow-y: scrollto implement vertical scrolling - Listen to touch operations through
touchstart,touchmove,touchendevents - Implement inertia scrolling algorithm for more natural scrolling
- Use
transform: scale()andopacityto provide visual feedback for selected items
setupWheelScroll(wheel, type) {
const itemHeight = 56;
let isTouching = false;
let velocity = 0;
let lastTimestamp = 0;
wheel.addEventListener('touchstart', (e) => {
isTouching = true;
velocity = 0;
lastTimestamp = performance.now();
}, { passive: true });
wheel.addEventListener('touchmove', (e) => {
if (!isTouching) return;
e.preventDefault();
// Calculate scrolling speed
const deltaTime = currentTimestamp - lastTimestamp;
if (deltaTime > 0) {
velocity = deltaY / deltaTime;
}
// Update scroll position
wheel.scrollTop += deltaY;
}, { passive: false });
wheel.addEventListener('touchend', () => {
isTouching = false;
// Apply inertia scrolling
if (Math.abs(velocity) > 0.3) {
this.applyInertiaScroll(wheel, type, itemHeight, velocity);
} else {
this.snapToItem(wheel, type, itemHeight);
}
}, { passive: true });
}
2. Inertia Scrolling Algorithm
To make the scrolling experience closer to native applications, physics-based inertia scrolling was implemented:
- Record the user's swipe speed
- Use friction coefficient (0.95) to simulate physical deceleration
- Automatically snap to the nearest option when speed falls below threshold
applyInertiaScroll(wheel, type, itemHeight, initialVelocity) {
let velocity = initialVelocity;
const friction = 0.95;
const minVelocity = 0.1;
const animate = () => {
wheel.scrollTop += velocity * 16;
velocity *= friction;
if (Math.abs(velocity) > minVelocity) {
requestAnimationFrame(animate);
} else {
this.snapToItem(wheel, type, itemHeight);
}
};
animate();
}
3. Multi-Language Support
The component supports 9 languages and can automatically adjust the date display order based on language:
- Year-Month-Day order: Chinese, Japanese, Korean
- Day-Month-Year order: English, Spanish, French, German, etc.
Language Adaptation Implementation:
generatePickerColumns(minYear, maxYear) {
const langCode = window.commonTranslations?.lang || 'en';
const isYearFirst = (langCode.startsWith('zh') || langCode === 'cn' ||
langCode.startsWith('ja') || langCode.startsWith('ko'));
if (isYearFirst) {
// Year, Month, Day
return `
<div class="picker-column">Year</div>
<div class="picker-column">Month</div>
<div class="picker-column">Day</div>
`;
} else {
// Day, Month, Year
return `
<div class="picker-column">Day</div>
<div class="picker-column">Month</div>
<div class="picker-column">Year</div>
`;
}
}
4. Smart Date Validation
Automatically handles changes in the number of days in different months:
getDaysInMonth(year, month) {
return new Date(year, month + 1, 0).getDate();
}
updateDayOptions() {
const daysInMonth = this.getDaysInMonth(this.currentYear, this.currentMonth);
// Dynamically generate day options based on current year and month
}
When users switch months, the component automatically updates the day options (e.g., February only has 28 or 29 days).
5. Responsive Design
Uses CSS media queries to adapt to different screen sizes:
@media (max-width: 768px) {
.modal-content {
max-width: 100%;
border-radius: 16px 16px 0 0;
}
.picker-columns {
width: 90%;
}
}
@media (max-width: 480px) {
.picker-columns {
width: 80%;
}
}
6. Zero-Dependency Pure Native Implementation
The entire component doesn't depend on any third-party libraries, using only:
- Native JavaScript (ES6+)
- Native CSS3
- Native DOM API
This makes the component:
- Small in size (~10KB when minified)
- Fast loading
- Easy to integrate
- No version conflict risks
Use Cases
1. Mobile Web Applications
Due to the iOS-style wheel interaction, it's particularly suitable for mobile scenarios:
- User registration/login (selecting birthday)
- Appointment systems (selecting date)
- E-commerce applications (selecting delivery date)
- Travel applications (selecting travel date)
2. Multi-Language Internationalization Projects
If your project needs to support multiple languages, this component works out of the box:
- No need to implement date pickers separately for each language
- Automatically adapts to different language date formats
- Supports right-to-left (RTL) languages (like Arabic)
3. Lightweight Projects
For projects that prioritize performance:
- Don't want to introduce large date libraries (like Moment.js, date-fns)
- Only need basic year-month-day selection functionality
- Have strict limitations on bundle size
4. Rapid Prototyping
Quickly implement functionality in the early stages of a project:
- Simple API calls
- No complex configuration required
- Fast integration
Usage Examples
Basic Usage
// Open the date picker
window.datePicker.show(
'Select Date', // Title
function(result) {
console.log('Year:', result.year);
console.log('Month:', result.month + 1);
console.log('Day:', result.day);
},
{
initialYear: new Date().getFullYear(),
initialMonth: new Date().getMonth(),
initialDay: new Date().getDate()
}
);
Setting Initial Date
window.datePicker.show(
'Pick a Date',
({ year, month, day }) => {
alert(`Selected: ${year}-${month + 1}-${day}`);
},
{
initialYear: 2025,
initialMonth: 0, // January
initialDay: 15
}
);
Multi-Language Support
// Set language
window.commonTranslations = {
lang: 'zh', // Chinese
datePicker: {
modalTitle: '选择日期',
confirmButton: '确认'
}
};
// Open the picker
window.datePicker.show(
'选择日期',
function(result) {
console.log('Selected:', result.year, result.month + 1, result.day);
}
);
Custom Styling
/* Modify confirm button color */
.modal-confirm {
background: #ff6b6b;
}
/* Modify selected item style */
.picker-wheel-item.selected .item-text {
color: #ff6b6b;
font-size: 22px;
}
/* Modify item height */
.picker-wheel-item {
height: 60px;
}
Technical Architecture
File Structure
Year-Month-Day-Date-Picker/
├── date-picker.js # Main component logic
├── date-formatter.js # Date formatting utility
├── date-picker.css # Component styles
├── example.js # Usage examples
└── index.html # Demo page
Core Class Design
DatePicker Class
-
show()- Display the picker -
hide()- Hide the picker -
confirm()- Confirm selection -
selectYear/Month/Day()- Select specific values -
updateDayOptions()- Update day options
DateFormatter Class
-
formatDate()- Format complete date -
formatYearMonth()- Format year and month
Event Handling Flow
User Interaction → Touch Events → Calculate Speed → Inertia Scroll → Snap Alignment → Update State → Visual Feedback
Performance Optimization
1. Event Throttling
Use requestAnimationFrame during scrolling to optimize rendering performance:
const animate = () => {
wheel.scrollTop += velocity * 16;
if (Math.abs(velocity) > minVelocity) {
requestAnimationFrame(animate);
}
};
2. CSS Hardware Acceleration
Use transform and opacity to trigger GPU acceleration:
.picker-wheel-item {
transition: opacity 0.3s ease, transform 0.3s ease;
}
3. Lazy DOM Loading
Only create picker DOM when needed to avoid unnecessary memory usage.
Browser Compatibility
- Chrome/Edge (latest)
- Firefox (latest)
- Safari (latest)
- Mobile browsers (iOS Safari, Chrome Mobile)
Project Repository
GitHub: https://github.com/lvayywyg-cmyk/Year-Month-Day-Date-Picker
Conclusion
This date picker component started from an actual project requirement and, through careful design and optimization, has become a lightweight, easy-to-use, and feature-complete solution. It demonstrates how to implement complex interactive effects using native technologies, while also proving that sometimes "building your own wheel" is more suitable than using existing solutions.
If you're looking for a lightweight date picker, or want to learn how to implement iOS-style wheel interactions, feel free to try out this project. If you're interested in Sudoku100 puzzle games, you're also welcome to visit https://www.sudoku100.com to experience it!
Related Links:
Top comments (0)