微件:ScrollText
来自卡厄思梦境WIKI
<style> .scroll-text {
position: relative; overflow: hidden; display: flex; align-items: flex-start;
}
/* 动画由 JS 动态设置 */ .scroll-text .scroll-text-content {
word-wrap: break-word; word-break: break-all; position: relative; width: 100%; text-align: center; will-change: transform;
}
.scroll-text .scroll-text-content:hover {
animation-play-state: paused;
} </style>
<script> (function() {
var processedElements = new WeakSet();
function initScroll() {
var wrappers = document.querySelectorAll('.scroll-text');
wrappers.forEach(function(wrapper, index) {
// 已完成动画绑定的元素跳过
if (processedElements.has(wrapper)) return;
var content = wrapper.querySelector('.scroll-text-content');
if (!content) return;
// 不可见容器跳过(等待视图显示时再初始化)
var rect = wrapper.getBoundingClientRect();
if (rect.width === 0 || rect.height === 0) return;
var speed = parseFloat(content.getAttribute('data-speed')) || 10;
var animationName = 'scrollText-' + index + '-' + Date.now();
setTimeout(function() {
var contentHeight = content.scrollHeight;
var wrapperHeight = wrapper.offsetHeight;
var scrollDistance = contentHeight - wrapperHeight;
// 内容未溢出,不绑定动画,也不标记为 processed,给后续机会
if (scrollDistance <= 0) return;
// 动态注入 keyframes
var style = document.createElement('style');
style.textContent =
'@keyframes ' + animationName + ' {' +
'0%{transform:translateY(0)}' +
'100%{transform:translateY(-' + scrollDistance + 'px)}' +
'}';
document.head.appendChild(style);
// 根据字数/速度估算时长
var text = content.textContent || content.innerText || ;
var charCount = text.replace(/\s+/g, ).length;
var duration = Math.max(speed, charCount / 8);
// 设置动画属性
content.style.animationName = animationName;
content.style.animationDuration = duration + 's';
content.style.animationTimingFunction = 'linear';
content.style.animationIterationCount = 'infinite';
content.style.animationPlayState = 'running';
// 仅在成功绑定动画后标记为已处理
processedElements.add(wrapper);
}, 50);
});
}
// 监听自定义事件(视图显示后主动触发初始化)
document.addEventListener('scrolltext:init', function () {
setTimeout(initScroll, 50);
});
// 监听 DOM 变化(模态和各子视图从隐藏到显示时触发)
var observer = new MutationObserver(function(mutations) {
var shouldInit = false;
mutations.forEach(function(mutation) {
if (mutation.type === 'attributes' && mutation.attributeName === 'style') {
var el = mutation.target;
var visible = el.style && el.style.display && el.style.display !== 'none';
if (visible && (
el.classList.contains('card-modal') ||
el.classList.contains('original-card-view') ||
el.classList.contains('inspiration-view') ||
el.classList.contains('god-inspiration-view') ||
el.classList.contains('subcards-view') ||
el.classList.contains('nested-subcards-view') ||
el.classList.contains('inspiration-subcards-view') ||
el.classList.contains('inspiration-nested-subcards-view')
)) {
shouldInit = true;
}
}
if (mutation.addedNodes && mutation.addedNodes.length > 0) {
shouldInit = true;
}
});
if (shouldInit) {
setTimeout(initScroll, 100);
}
});
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['style']
});
// 初始加载
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() {
setTimeout(initScroll, 100);
});
} else {
setTimeout(initScroll, 100);
}
// MediaWiki 钩子
if (typeof mw !== 'undefined' && mw.hook) {
mw.hook('wikipage.content').add(function() {
setTimeout(initScroll, 100);
});
}
})(); </script>