<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<div id="my-slider" class="slider-list"></div>
</body>
</html>
/*
表现:CSS
1、使用 CSS 绝对定位将图片重叠在同一位置
2、轮播图切换的状态使用修饰符(modifier)
3、轮播图的切换动画使用 CSS transition
*/
#my-slider {
position: relative;
width: 800px;
height: 300px;
}
.slider-list ul {
list-style-type: none;
position: relative;
margin: 0;
padding: 0;
}
.slider-list__item,
.slider-list__item--selected {
position: absolute;
transition: opacity 1s;
opacity: 0;
text-align: center;
}
.slider-list__item--selected {
transition: opacity 1s;
opacity: 1;
}
/* 轮播图交互元素 */
.slide-list__previous,
.slide-list__next {
position: absolute;
top: 50%;
margin-top: -25px;
display: inline-block;
width: 30px;
height: 50px;
text-align: center;
font-size: 24px;
line-height: 50px;
overflow: hidden;
border: none;
background: transparent;
color: white;
background: rgba(0, 0, 0, 0.2);
cursor: pointer;
opacity: 0;
transition: opacity 0.5s;
}
.slide-list__previous {
left: 0;
}
.slide-list__previous::after {
content: "<";
}
.slide-list__next {
right: 0;
}
.slide-list__next::after {
content: ">";
}
#my-slider:hover .slide-list__previous,
#my-slider:hover .slide-list__next {
opacity: 1;
}
.slide-list__control {
position: relative;
display: table;
margin: auto;
background-color: rgba(255, 255, 255, 0.5);
padding: 3px;
border-radius: 12px;
top:300px;
/*
不知道什么原因
设置 bottom:30px;
slide-list__control 向上溢出 slider-list
*/
}
.slide-list__control-buttons,
.slide-list__control-buttons--selected {
display: inline-block;
width: 15px;
height: 15px;
border-radius: 50%;
margin: 0 5px;
background-color: white;
cursor: pointer;
}
.slide-list__control-buttons--selected {
background-color: red;
}
/*
行为:JS
1. API 设计应保证原子操作,职责单一,满足灵活性。
2. Slider
2.1. getSelectedItem()
2.2. getSelectedItemIndex()
2.3. slideTo()
2.4. slideNext()
2.5. slidePrevious()
*/
class Component {
constructor(id, opts = { name, data: [] }) {
this.container = document.getElementById(id);
this.options = opts;
this.container.innerHTML = this.render(opts.data);
}
// 注册插件
registerPlugins(plugins) {
plugins.forEach(plugin => {
const pluginContainer = document.createElement("div");
pluginContainer.className = `.${name}__plugin`;
pluginContainer.innerHTML = plugin.render(this.options.data);
this.container.appendChild(pluginContainer);
plugin.action(this);
});
}
render(data) {
/* abstract */
return "";
}
}
class Slider extends Component {
constructor(id, opts = { name: "slider-list", data: [], cycle: 3000 }) {
super(id, opts);
/*
this.items 指向一个 non-live 的 nodeList (由元素节点组成的 array like object)
该 nodeList 是一个静态对象,并不会随着所选元素的变化而变化
*/
this.items = this.container.querySelectorAll(
".slider-list__item, .slider-list__item--selected"
);
// 轮播间隔
this.cycle = opts.cycle || 3000;
// 类实例化时,轮播图默认跳转至第一项
this.slideTo(0);
}
/*
根据 opts.images 数据
渲染轮播图页面结构
*/
render(data) {
const content = data.map(image =>
`
<li class="slider-list__item">
<img src="${image}" />
</li>
`.trim()
);
/*
content.join('')
数组转字符串
并将分隔符 , 替换为 ''
*/
return `<ul>${content.join("")}</ul>`;
}
getSelectedItem() {
const selected = this.container.querySelector(
".slider-list__item--selected"
);
return selected;
}
getSelectedItemIndex() {
/*
Array.from()
可将 arrayLike(类似数组或可迭代对象)创建一个新的,浅拷贝的数组实例
*/
return Array.from(this.items).indexOf(this.getSelectedItem());
}
slideTo(idx) {
const selected = this.getSelectedItem();
if (selected) {
selected.className = "slider-list__item";
}
/*
NodeList.item()
返回 NodeList 对象中指定索引的节点,如果索引越界,则返回 null。
等价写法为:nodeList[index],这种情况下,越界访问将返回 undefined。
*/
const item = this.items[idx];
if (item) {
item.className = "slider-list__item--selected";
}
/*
this.container
添加自定义事件
实现 slide-list__control 与 slide-list 解耦
*/
const detail = { index: idx };
const slideEvent = new CustomEvent("slide", {
bubbles: true,
detail,
});
this.container.dispatchEvent(slideEvent);
}
slideNext() {
const currentIdx = this.getSelectedItemIndex();
/*
考虑到 nodeList 最后一项索引 + 1,
与 nodeList 长度取余,
获取 nodeList 第一项索引
*/
const nextIdx = (currentIdx + 1) % this.items.length;
this.slideTo(nextIdx);
}
slidePrevious() {
const currentIdx = this.getSelectedItemIndex();
/*
考虑到 nodeList 第一项索引 - 1
加上 nodeList 长度,再与其取余
获取 nodeList 最后一项索引
*/
const previousIdx =
(this.items.length + currentIdx - 1) & this.items.length;
this.slideTo(previousIdx);
}
start() {
this.stop();
this._timer = setInterval(() => this.slideNext(), this.cycle);
}
stop() {
clearInterval(this._timer);
}
}
// 交互逻辑对象
const pluginController = {
// HTML 结构渲染方法
render(images) {
return `
<div class="slide-list__control">
${images
.map(
(image, index) =>
`
<span
class="slide-list__control-buttons${
index === 0 ? "--selected" : ""
}">
</span>
`
)
.join("")}
</div>
`;
},
// 交互事件方法
action(slider) {
const controller = slider.container.querySelector(
".slide-list__control"
);
if (controller) {
const buttons = controller.querySelectorAll(
".slide-list__control-buttons, .slide-list__control-buttons--selected"
);
controller.addEventListener("mouseover", evt => {
const idx = Array.from(buttons).indexOf(evt.target);
if (idx >= 0) {
slider.slideTo(idx);
slider.stop();
}
});
controller.addEventListener("mouseout", evt => {
slider.start();
});
slider.container.addEventListener("slide", evt => {
const idx = evt.detail.index;
const selected = controller.querySelector(
".slide-list__control-buttons--selected"
);
if (selected) {
selected.className = "slide-list__control-buttons";
}
buttons[idx].className = "slide-list__control-buttons--selected";
});
}
},
};
const pluginPrevious = {
render() {
return `<a class="slide-list__previous"></a>`;
},
action(slider) {
const previous = slider.container.querySelector(
".slide-list__previous"
);
if (previous) {
previous.addEventListener("click", evt => {
slider.stop();
slider.slidePrevious();
slider.start();
evt.preventDefault();
});
}
},
};
const pluginNext = {
render() {
return `<a class="slide-list__next"></a>`;
},
action(slider) {
const next = slider.container.querySelector(".slide-list__next");
if (next) {
next.addEventListener("click", evt => {
slider.stop();
slider.slideNext();
slider.start();
evt.preventDefault();
});
}
},
};
const slider = new Slider("my-slider", {
name: "slider-list",
data: [
'https://p5.ssl.qhimg.com/t0119c74624763dd070.png',
'https://p4.ssl.qhimg.com/t01adbe3351db853eb3.jpg',
'https://p2.ssl.qhimg.com/t01645cd5ba0c3b60cb.jpg',
'https://p4.ssl.qhimg.com/t01331ac159b58f5478.jpg'
],
cycle: 3000,
});
// 注册插件
slider.registerPlugins(pluginController, pluginPrevious, pluginNext);
slider.start();
Output
You can jump to the latest bin by adding /latest
to your URL
Keyboard Shortcuts
Shortcut | Action |
---|---|
ctrl + [num] | Toggle nth panel |
ctrl + 0 | Close focused panel |
ctrl + enter | Re-render output. If console visible: run JS in console |
Ctrl + l | Clear the console |
ctrl + / | Toggle comment on selected lines |
ctrl + ] | Indents selected lines |
ctrl + [ | Unindents selected lines |
tab | Code complete & Emmet expand |
ctrl + shift + L | Beautify code in active panel |
ctrl + s | Save & lock current Bin from further changes |
ctrl + shift + s | Open the share options |
ctrl + y | Archive Bin |
Complete list of JS Bin shortcuts |
JS Bin URLs
URL | Action |
---|---|
/ | Show the full rendered output. This content will update in real time as it's updated from the /edit url. |
/edit | Edit the current bin |
/watch | Follow a Code Casting session |
/embed | Create an embeddable version of the bin |
/latest | Load the very latest bin (/latest goes in place of the revision) |
/[username]/last | View the last edited bin for this user |
/[username]/last/edit | Edit the last edited bin for this user |
/[username]/last/watch | Follow the Code Casting session for the latest bin for this user |
/quiet | Remove analytics and edit button from rendered output |
.js | Load only the JavaScript for a bin |
.css | Load only the CSS for a bin |
Except for username prefixed urls, the url may start with http://jsbin.com/abc and the url fragments can be added to the url to view it differently. |