Vertical notice tickers look simple until you try to build one without a library. A quick demo is enough to see the intended motion: a fixed-height notice box shows one line at a time; the current item slides upward, disappears, and is appended to the end of the list so the cycle can continue.
Most ready-made seamless ticker styles available online depend on jQuery, so the implementation below rewrites that approach in native JavaScript. The core idea is to animate marginTop from 0 to -35px over 500ms, reset marginTop to 0 when the animation finishes, and move the first list item to the end of the list. The CSS container is 35px high with overflow: hidden, which makes the item appear to slide out of view while the next item rises into place.
The jQuery version being replaced
The original animation is compact: animate() moves the list upward, and the callback resets the margin and recycles the first item.
/*
* Parameters:
* obj: the animated node, a ul in this example
* top: animation height, -35px here; upward scrolling uses a negative value
* time: animation duration in milliseconds, 500 here, meaning marginTop goes from 0 to -35px in 500 ms
* function: callback; after each animation marginTop resets to zero and the first item is appended to the end of the list
*
*/
function noticeUp(obj,top,time) {
$(obj).animate({
marginTop: top
}, time, function () {
$(this).css({marginTop:"0"}).find(":first").appendTo(this);
})
}
jQuery is undeniably convenient here. The native version below takes several times more code, mainly because the animation has to be driven by hand.
A native JavaScript version
The rewritten function uses requestAnimationFrame() and an easing helper. On each frame it calculates the current marginTop; when the duration is complete, it sets marginTop back to 0px and appends the first element to the end of the list.
function noticeUp(obj, top, time) {
var element = document.querySelector(obj);
var start = null;
var targetMarginTop = parseInt(top, 10);
var initialMarginTop = parseInt(window.getComputedStyle(element).marginTop, 10);
var changeInValue = targetMarginTop - initialMarginTop;
var currentTime = 0;
var increment = 20;
var animateMargin = function (timestamp) {
if (!start) start = timestamp;
currentTime += increment;
var val = Math.easeInOutQuad(currentTime, initialMarginTop, changeInValue, time);
element.style.marginTop = val + 'px';
if (currentTime < time) {
requestAnimationFrame(animateMargin);
} else {
element.style.marginTop = '0px';
element.appendChild(element.firstElementChild);
}
};
// t = current time, b = start value, c = change in value, d = duration
Math.easeInOutQuad = function (t, b, c, d) {
t /= d / 2;
if (t < 1) return c / 2 * t * t + b;
t--;
return -c / 2 * (t * (t - 2) - 1) + b;
};
requestAnimationFrame(animateMargin);
}
The easing function is the same easeInOutQuad calculated from t, b, c, and d. The callback is replaced by a check on currentTime < time; when that condition no longer holds, the animation stops and the first child is recycled.
Full page example
The complete page combines the fixed-height container, a single-line list, the native animation function, and a setInterval that calls it every two seconds.
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>文字从下到上无缝轮播</title>
<style>
div,
ul,
li {
margin: 0;
padding: 0
}
/* Initialize default styles first */
.notice {
width: 300px;
/* Single-line display with overflow hidden */
height: 35px;
/* Fixed height for the notice area */
padding: 0 30px;
background-color: #b3effe;
overflow: hidden;
}
.notice ul li {
list-style: none;
line-height: 35px;
/* Below keeps it to a single line with overflow hidden */
display: block;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
</style>
</head>
<body>
<div class="notice">
<ul>
<li>第1条公告第1条公告第1条公告第1条公告第1条公告第1条公告</li>
<li>第2条公告第2条公告第2条公告第2条公告第2条公告第2条公告</li>
<li>第3条公告第3条公告第3条公告第3条公告第3条公告第3条公告</li>
<li>第4条公告第4条公告第4条公告第4条公告第4条公告第4条公告</li>
</ul>
</div>
<script>
function noticeUp(obj, top, time) {
var element = document.querySelector(obj);
var start = null;
var targetMarginTop = parseInt(top, 10);
var initialMarginTop = parseInt(window.getComputedStyle(element).marginTop, 10);
var changeInValue = targetMarginTop - initialMarginTop;
var currentTime = 0;
var increment = 20;
var animateMargin = function (timestamp) {
if (!start) start = timestamp;
currentTime += increment;
var val = Math.easeInOutQuad(currentTime, initialMarginTop, changeInValue, time);
element.style.marginTop = val + 'px';
if (currentTime < time) {
requestAnimationFrame(animateMargin);
} else {
element.style.marginTop = '0px';
element.appendChild(element.firstElementChild);
}
};
// t = current time, b = start value, c = change in value, d = duration
Math.easeInOutQuad = function (t, b, c, d) {
t /= d / 2;
if (t < 1) return c / 2 * t * t + b;
t--;
return -c / 2 * (t * (t - 2) - 1) + b;
};
requestAnimationFrame(animateMargin);
}
setInterval("noticeUp('.notice ul','-35px',500)", 2000);
</script>
</body>
</html>
The list item text is left as placeholder Chinese strings; replace them with the announcements you need. The timing is also easy to adjust: -35px matches the 35px container height, 500 is the animation duration in milliseconds, and the interval 2000 controls how long each item stays visible before the next move.