Component Structure
To implement an auto-scrolling carousel, the markup requires a container wrapper that hides content exceeding its boundaries and an internal list element to hold the moving items.
<div class="scroll-container">
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
</div>
Core Mechanics
The scrolling effect relies on manipulating CSS offset properties via JavaScript. The container must have overflow: hidden applied to mask off-screen elements. The movement logic determines whether to adjust horizontal (margin-left) or vertical (margin-top) offsets based on configuration.
For the animation to appear continuous, we calculate the total dimension of the inner list by multiplying the size of a single item by the total count. When the movement distance surpasses the size of one item, we perform a DOM relocation: move the first child node to the end of the list and instant reset the margin offset to zero. This swap happens outside human perception, creating an infinite loop illusion.
Scrolling only activates when the inner content width/height exceeds the container's viewport dimensions; otherwise, no animation is required.
Plugin Implementation
The following code defines a custom jQuery plugin named ticker. It supports both axis directions and includes pause-on-hover functionality.
(function($) {
$.fn.ticker = function(opts) {
var settings = $.extend({
interval: 50,
axis: 'x'
}, opts);
return this.each(function() {
var $wrapper = $(this);
var $list = $wrapper.find('> ul');
var $items = $list.children('li');
var itemCount = $items.length;
var singleSize;
var totalSize;
var viewportSize;
var cssProp;
var animTarget;
var timerId;
// Configure based on direction
if (settings.axis === 'x') {
viewportSize = $wrapper.width();
cssProp = 'marginLeft';
singleSize = $items.first().outerWidth(true);
totalSize = singleSize * itemCount;
animTarget = { marginLeft: '-=1' };
$list.css('width', totalSize);
} else {
viewportSize = $wrapper.height();
cssProp = 'marginTop';
singleSize = $items.first().outerHeight(true);
totalSize = singleSize * itemCount;
animTarget = { marginTop: '-=1' };
$list.css('height', totalSize);
}
// Start loop if overflow exists
if (totalSize > viewportSize) {
timerId = setInterval(function() {
$list.animate(animTarget, 0, function() {
var currentOffset = Math.abs(parseInt($list.css(cssProp), 10));
// Reset point check
if (currentOffset > singleSize) {
$items.first().appendTo($list);
$list.css(cssProp, 0);
}
});
}, settings.interval);
// Interaction handlers
$wrapper.on('mouseenter', function() {
clearInterval(timerId);
}).on('mouseleave', function() {
timerId = setInterval(function() {
$list.animate(animTarget, 0, function() {
var currentOffset = Math.abs(parseInt($list.css(cssProp), 10));
if (currentOffset > singleSize) {
$items.first().appendTo($list);
$list.css(cssProp, 0);
}
});
}, settings.interval);
});
}
});
};
}(jQuery));
Usage Example
HTML:
<div class="horizontal-ticker">
<ul>
<li><a href="#">Link 1</a></li>
<li><a href="#">Link 2</a></li>
<li><a href="#">Link 3</a></li>
<li><a href="#">Link 4</a></li>
<li><a href="#">Link 5</a></li>
</ul>
</div>
<div class="vertical-ticker">
<ul>
<li><a href="#">Up 1</a></li>
<li><a href="#">Up 2</a></li>
<li><a href="#">Up 3</a></li>
</ul>
</div>
<script>
$(document).ready(function() {
$('.horizontal-ticker').ticker({ interval: 30, axis: 'x' });
$('.vertical-ticker').ticker({ axis: 'y' });
});
</script>
CSS Styling:
body {
font-family: sans-serif;
}
.horizontal-ticker,
.vertical-ticker {
overflow: hidden;
padding: 10px;
border: 1px solid #ccc;
margin: 20px auto;
}
.horizontal-ticker {
width: 600px;
height: 80px;
}
.vertical-ticker {
width: 150px;
height: 300px;
}
li {
float: left;
list-style: none;
}
.vertical-ticker li {
float: none;
clear: both;
}
a {
display: block;
width: 120px;
height: 80px;
line-height: 80px;
text-align: center;
text-decoration: none;
color: #333;
}
.vertical-ticker a {
width: 100%;
height: 75px;
}