0
0
mirror of https://github.com/twbs/bootstrap.git synced 2025-01-29 21:52:22 +01:00

Add touch support in our carousel with HammerJS.

This commit is contained in:
Johann-S 2018-03-03 23:04:11 +02:00 committed by XhmikosR
parent bf57389647
commit caefd70463
17 changed files with 267 additions and 41 deletions

View File

@ -99,7 +99,7 @@ bootstrap/
└── bootstrap.min.js.map
```
We provide compiled CSS and JS (`bootstrap.*`), as well as compiled and minified CSS and JS (`bootstrap.min.*`). [source maps](https://developers.google.com/web/tools/chrome-devtools/debug/readability/source-maps) (`bootstrap.*.map`) are available for use with certain browsers' developer tools. Bundled JS files (`bootstrap.bundle.js` and minified `bootstrap.bundle.min.js`) include [Popper](https://popper.js.org/), but not [jQuery](https://jquery.com/).
We provide compiled CSS and JS (`bootstrap.*`), as well as compiled and minified CSS and JS (`bootstrap.min.*`). [source maps](https://developers.google.com/web/tools/chrome-devtools/debug/readability/source-maps) (`bootstrap.*.map`) are available for use with certain browsers' developer tools. Bundled JS files (`bootstrap.bundle.js` and minified `bootstrap.bundle.min.js`) include [Popper](https://popper.js.org/) and [HammerJS](https://hammerjs.github.io/), but not [jQuery](https://jquery.com/).
## Bugs and feature requests

View File

@ -58,6 +58,8 @@ cdn:
jquery_hash: "sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
popper: "https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.4/umd/popper.min.js"
popper_hash: "sha384-GM0Y80ecpwKxF1D5XCrGanKusGDy9WW0O2sSM84neB4iFhvKp3fwnoIRnPsQcN1R"
hammer: "https://cdnjs.cloudflare.com/ajax/libs/hammer.js/2.0.8/hammer.min.js"
hammer_hash: "sha384-Cs3dgUx6+jDxxuqHvVH8Onpyj2LF1gKZurLDlhqzuJmUqVYMJ0THTWpxK5Z086Zm"
toc:
min_level: 2

View File

@ -5,10 +5,10 @@
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
const path = require('path')
const rollup = require('rollup')
const babel = require('rollup-plugin-babel')
const banner = require('./banner.js')
const path = require('path')
const rollup = require('rollup')
const babel = require('rollup-plugin-babel')
const banner = require('./banner.js')
const TEST = process.env.NODE_ENV === 'test'
const plugins = [
@ -41,8 +41,9 @@ const rootPath = TEST ? '../js/coverage/dist/' : '../js/dist/'
function build(plugin) {
console.log(`Building ${plugin} plugin...`)
const external = ['jquery', 'popper.js']
const external = ['hammerjs', 'jquery', 'popper.js']
const globals = {
hammerjs: 'Hammer',
jquery: 'jQuery', // Ensure we use jQuery which is always available even in noConflict mode
'popper.js': 'Popper'
}

View File

@ -42,6 +42,10 @@ const files = [
{
file: 'node_modules/popper.js/dist/umd/popper.min.js',
configPropertyName: 'popper_hash'
},
{
file: 'node_modules/hammerjs/hammer.min.js',
configPropertyName: 'hammer_hash'
}
]

View File

@ -1,12 +1,13 @@
const path = require('path')
const babel = require('rollup-plugin-babel')
const resolve = require('rollup-plugin-node-resolve')
const banner = require('./banner.js')
const path = require('path')
const babel = require('rollup-plugin-babel')
const resolve = require('rollup-plugin-node-resolve')
const commonjs = require('rollup-plugin-commonjs')
const banner = require('./banner.js')
const BUNDLE = process.env.BUNDLE === 'true'
let fileDest = 'bootstrap.js'
const external = ['jquery', 'popper.js']
let fileDest = 'bootstrap.js'
const external = ['jquery', 'hammerjs', 'popper.js']
const plugins = [
babel({
exclude: 'node_modules/**', // Only transpile our source code
@ -21,15 +22,22 @@ const plugins = [
]
const globals = {
jquery: 'jQuery', // Ensure we use jQuery which is always available even in noConflict mode
hammerjs: 'Hammer',
'popper.js': 'Popper'
}
if (BUNDLE) {
fileDest = 'bootstrap.bundle.js'
// Remove last entry in external array to bundle Popper
external.pop()
// We just keep jQuery as external
external.length = 1
delete globals['popper.js']
plugins.push(resolve())
delete globals.hammerjs
plugins.push(
commonjs({
include: 'node_modules/**'
}),
resolve()
)
}
module.exports = {

View File

@ -1,4 +1,5 @@
import $ from 'jquery'
import Hammer from 'hammerjs'
import Util from './util'
/**
@ -23,13 +24,15 @@ const JQUERY_NO_CONFLICT = $.fn[NAME]
const ARROW_LEFT_KEYCODE = 37 // KeyboardEvent.which value for left arrow key
const ARROW_RIGHT_KEYCODE = 39 // KeyboardEvent.which value for right arrow key
const TOUCHEVENT_COMPAT_WAIT = 500 // Time for mouse compat events to fire after touch
const HAMMER_ENABLED = typeof Hammer !== 'undefined'
const Default = {
interval : 5000,
keyboard : true,
slide : false,
pause : 'hover',
wrap : true
wrap : true,
touch : true
}
const DefaultType = {
@ -37,7 +40,8 @@ const DefaultType = {
keyboard : 'boolean',
slide : '(boolean|string)',
pause : '(string|boolean)',
wrap : 'boolean'
wrap : 'boolean',
touch : 'boolean'
}
const Direction = {
@ -55,7 +59,9 @@ const Event = {
MOUSELEAVE : `mouseleave${EVENT_KEY}`,
TOUCHEND : `touchend${EVENT_KEY}`,
LOAD_DATA_API : `load${EVENT_KEY}${DATA_API_KEY}`,
CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}`
CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}`,
SWIPELEFT : 'swipeleft',
SWIPERIGHT : 'swiperight'
}
const ClassName = {
@ -84,21 +90,30 @@ const Selector = {
* Class Definition
* ------------------------------------------------------------------------
*/
class Carousel {
constructor(element, config) {
this._items = null
this._interval = null
this._activeElement = null
this._items = null
this._interval = null
this._activeElement = null
this._isPaused = false
this._isSliding = false
this.touchTimeout = null
this.hammer = null
this._isPaused = false
this._isSliding = false
this._config = this._getConfig(config)
this._element = element
this._indicatorsElement = this._element.querySelector(Selector.INDICATORS)
this._touchSupported = 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0
this.touchTimeout = null
this._config = this._getConfig(config)
this._element = $(element)[0]
this._indicatorsElement = this._element.querySelector(Selector.INDICATORS)
if (HAMMER_ENABLED && this._touchSupported && this._config.touch) {
this.hammer = new Hammer(this._element, {
recognizers: [[
Hammer.Swipe, {
direction: Hammer.DIRECTION_HORIZONTAL
}
]]
})
}
this._addEventListeners()
}
@ -226,11 +241,16 @@ class Carousel {
.on(Event.KEYDOWN, (event) => this._keydown(event))
}
if (this.hammer) {
this.hammer.on(Event.SWIPELEFT, () => this.next())
this.hammer.on(Event.SWIPERIGHT, () => this.prev())
}
if (this._config.pause === 'hover') {
$(this._element)
.on(Event.MOUSEENTER, (event) => this.pause(event))
.on(Event.MOUSELEAVE, (event) => this.cycle(event))
if ('ontouchstart' in document.documentElement) {
if (this._touchSupported) {
// If it's a touch-enabled device, mouseenter/leave are fired as
// part of the mouse compatibility events on first tap - the carousel
// would stop cycling until user tapped out of it;

View File

@ -20,6 +20,7 @@
}())
</script>
<script src="../../node_modules/popper.js/dist/umd/popper.min.js"></script>
<script src="../../node_modules/hammerjs/hammer.min.js"></script>
<!-- QUnit -->
<link rel="stylesheet" href="../../node_modules/qunit/qunit/qunit.css" media="screen">
@ -28,6 +29,9 @@
<!-- Sinon -->
<script src="../../node_modules/sinon/pkg/sinon-no-sourcemaps.js"></script>
<!-- Hammer simulator -->
<script src="../../node_modules/hammer-simulator/index.js"></script>
<script>
// Disable jQuery event aliases to ensure we don't accidentally use any of them
[

View File

@ -12,16 +12,16 @@ const jqueryFile = process.env.USE_OLD_JQUERY ? 'https://code.jquery.com/jquery-
const bundle = process.env.BUNDLE === 'true'
const browserStack = process.env.BROWSER === 'true'
const frameworks = [
'qunit',
'sinon'
]
const plugins = [
'karma-qunit',
'karma-sinon'
]
const frameworks = [
'qunit',
'sinon'
]
const reporters = ['dots']
const detectBrowsers = {
@ -46,7 +46,12 @@ const customLaunchers = {
}
}
let files = ['node_modules/popper.js/dist/umd/popper.min.js']
let files = [
'node_modules/popper.js/dist/umd/popper.min.js',
'node_modules/hammerjs/hammer.min.js',
'node_modules/hammer-simulator/index.js'
]
const conf = {
basePath: '../..',
port: 9876,

View File

@ -9,7 +9,9 @@
"sinon": false,
"Util": false,
"Alert": false,
"Button": false
"Button": false,
"Carousel": false,
"Simulator": false
},
"parserOptions": {
"ecmaVersion": 5,

View File

@ -1,6 +1,10 @@
$(function () {
'use strict'
window.Carousel = typeof bootstrap !== 'undefined' ? bootstrap.Carousel : Carousel
var touchSupported = 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0
QUnit.module('carousel plugin')
QUnit.test('should be defined on jQuery object', function (assert) {
@ -25,6 +29,20 @@ $(function () {
assert.strictEqual(typeof $.fn.carousel, 'undefined', 'carousel was set back to undefined (orig value)')
})
QUnit.test('should return version', function (assert) {
assert.expect(1)
assert.strictEqual(typeof Carousel.VERSION, 'string')
})
QUnit.test('should return default parameters', function (assert) {
assert.expect(1)
var defaultConfig = Carousel.Default
assert.strictEqual(defaultConfig.touch, true)
})
QUnit.test('should throw explicit error on undefined method', function (assert) {
assert.expect(1)
var $el = $('<div/>')
@ -989,4 +1007,113 @@ $(function () {
}, 80)
}, 80)
})
QUnit.test('should allow swiperight and call prev', function (assert) {
if (!touchSupported) {
assert.expect(0)
return
}
assert.expect(2)
var done = assert.async()
document.documentElement.ontouchstart = $.noop
var carouselHTML =
'<div class="carousel" data-interval="false">' +
' <div class="carousel-inner">' +
' <div id="item" class="carousel-item">' +
' <img alt="">' +
' </div>' +
' <div class="carousel-item active">' +
' <img alt="">' +
' </div>' +
' </div>' +
'</div>'
var $carousel = $(carouselHTML)
$carousel.appendTo('#qunit-fixture')
var $item = $('#item')
$carousel.bootstrapCarousel()
$carousel.one('slid.bs.carousel', function () {
assert.ok(true, 'slid event fired')
assert.ok($item.hasClass('active'))
delete document.documentElement.ontouchstart
done()
})
Simulator.gestures.swipe($carousel[0], {
deltaX: 300,
deltaY: 0
})
})
QUnit.test('should not use HammerJS when touch option is false', function (assert) {
assert.expect(1)
var $carousel = $('<div></div>').appendTo('#qunit-fixture')
$carousel.bootstrapCarousel({
touch: false
})
var carousel = $carousel.data('bs.carousel')
assert.strictEqual(carousel.hammer, null)
})
QUnit.test('should use HammerJS when touch option is true', function (assert) {
assert.expect(1)
document.documentElement.ontouchstart = $.noop
var $carousel = $('<div></div>').appendTo('#qunit-fixture')
$carousel.bootstrapCarousel()
var carousel = $carousel.data('bs.carousel')
assert.ok(carousel.hammer !== null)
})
QUnit.test('should allow swipeleft and call next', function (assert) {
if (!touchSupported) {
assert.expect(0)
return
}
assert.expect(2)
var done = assert.async()
document.documentElement.ontouchstart = $.noop
var carouselHTML =
'<div class="carousel" data-interval="false">' +
' <div class="carousel-inner">' +
' <div id="item" class="carousel-item active">' +
' <img alt="">' +
' </div>' +
' <div class="carousel-item">' +
' <img alt="">' +
' </div>' +
' </div>' +
'</div>'
var $carousel = $(carouselHTML)
$carousel.appendTo('#qunit-fixture')
var $item = $('#item')
$carousel.bootstrapCarousel()
$carousel.one('slid.bs.carousel', function () {
assert.ok(true, 'slid event fired')
assert.ok(!$item.hasClass('active'))
delete document.documentElement.ontouchstart
done()
})
Simulator.gestures.swipe($carousel[0], {
pos: [300, 10],
deltaX: -300,
deltaY: 0
})
})
})

View File

@ -46,6 +46,7 @@
</div>
<script src="../../../site/docs/4.1/assets/js/vendor/jquery-slim.min.js"></script>
<script src="../../../node_modules/hammerjs/hammer.min.js"></script>
<script src="../../dist/util.js"></script>
<script src="../../dist/carousel.js"></script>
<script>

39
package-lock.json generated
View File

@ -4812,6 +4812,18 @@
"pify": "^3.0.0"
}
},
"hammer-simulator": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/hammer-simulator/-/hammer-simulator-0.0.1.tgz",
"integrity": "sha1-7tO85CtDMgF1o/T4NP4gjl+iSho=",
"dev": true
},
"hammerjs": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz",
"integrity": "sha1-BO93hiz/K7edMPdpIJWTAiK/YPE=",
"dev": true
},
"handlebars": {
"version": "4.0.12",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.12.tgz",
@ -6443,6 +6455,15 @@
"yallist": "^2.1.2"
}
},
"magic-string": {
"version": "0.22.5",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.22.5.tgz",
"integrity": "sha512-oreip9rJZkzvA8Qzk9HFs8fZGF/u7H/gtrE8EN6RjKJ9kh2HlC+yQ2QezifqTZfGyiuAV0dRv5a+y/8gBb1m9w==",
"dev": true,
"requires": {
"vlq": "^0.2.2"
}
},
"make-dir": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz",
@ -8995,6 +9016,18 @@
"rollup-pluginutils": "^2.3.0"
}
},
"rollup-plugin-commonjs": {
"version": "9.1.6",
"resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-9.1.6.tgz",
"integrity": "sha512-J7GOJm9uzEeLqkVxYSgjyoieh34hATWpa9G2M1ilGzWOLYGfQx5IDQ9ewG8QUj/Z2dzgV+d0/AyloAzElkABAA==",
"dev": true,
"requires": {
"estree-walker": "^0.5.1",
"magic-string": "^0.22.4",
"resolve": "^1.5.0",
"rollup-pluginutils": "^2.0.1"
}
},
"rollup-plugin-node-resolve": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-3.4.0.tgz",
@ -11465,6 +11498,12 @@
"unist-util-stringify-position": "^1.1.1"
}
},
"vlq": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/vlq/-/vlq-0.2.3.tgz",
"integrity": "sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==",
"dev": true
},
"vnu-jar": {
"version": "18.8.29",
"resolved": "https://registry.npmjs.org/vnu-jar/-/vnu-jar-18.8.29.tgz",

View File

@ -95,6 +95,7 @@
"license": "MIT",
"dependencies": {},
"peerDependencies": {
"hammerjs": "^2.0.8",
"jquery": "1.9.1 - 3",
"popper.js": "^1.14.4"
},
@ -114,6 +115,8 @@
"eslint": "^5.7.0",
"find-unused-sass-variables": "^0.2.1",
"glob": "^7.1.3",
"hammer-simulator": "0.0.1",
"hammerjs": "^2.0.8",
"htmllint-cli": "^0.0.7",
"http-server": "^0.11.1",
"ip": "^1.1.5",
@ -133,6 +136,7 @@
"qunit": "^2.7.0",
"rollup": "^0.66.6",
"rollup-plugin-babel": "^4.0.3",
"rollup-plugin-commonjs": "^9.1.6",
"rollup-plugin-node-resolve": "^3.4.0",
"shelljs": "^0.8.2",
"shx": "^0.3.2",
@ -181,11 +185,11 @@
},
{
"path": "./dist/js/bootstrap.bundle.js",
"maxSize": "45 kB"
"maxSize": "61 kB"
},
{
"path": "./dist/js/bootstrap.bundle.min.js",
"maxSize": "25 kB"
"maxSize": "28 kB"
},
{
"path": "./dist/js/bootstrap.js",

View File

@ -1,5 +1,6 @@
<script src="{{ site.cdn.jquery }}" integrity="{{ site.cdn.jquery_hash }}" crossorigin="anonymous"></script>
<script>window.jQuery || document.write('<script src="{{ site.baseurl }}/assets/js/vendor/jquery-slim.min.js"><\/script>')</script>
<script src="{{ site.cdn.hammer }}"{% if site.github %} integrity="{{ site.cdn.hammer_hash }}" crossorigin="anonymous"{% endif %}></script>
{%- if jekyll.environment == "production" -%}
<script src="{{ site.baseurl }}/docs/{{ site.docs_version }}/dist/js/bootstrap.bundle.min.js" integrity="{{ site.cdn.js_bundle_hash }}" crossorigin="anonymous"></script>

View File

@ -12,6 +12,8 @@ The carousel is a slideshow for cycling through a series of content, built with
In browsers where the [Page Visibility API](https://www.w3.org/TR/page-visibility/) is supported, the carousel will avoid sliding when the webpage is not visible to the user (such as when the browser tab is inactive, the browser window is minimized, etc.).
The carousel supports swipe gestures (left and right) using [HammerJS]({{ site.cdn.hammer }}). For this to function correctly you need to include HammerJS before Bootstrap or use `bootstrap.bundle.min.js` / `bootstrap.bundle.js` which contains HammerJS.
Please be aware that nested carousels are not supported, and carousels are generally not compliant with accessibility standards.
Lastly, if you're building our JavaScript from source, it [requires `util.js`]({{ site.baseurl }}/docs/{{ site.docs_version }}/getting-started/javascript/#util).
@ -281,6 +283,12 @@ Options can be passed via data attributes or JavaScript. For data attributes, ap
<td>true</td>
<td>Whether the carousel should cycle continuously or have hard stops.</td>
</tr>
<tr>
<td>touch</td>
<td>boolean</td>
<td>true</td>
<td>Whether the carousel should handle touch event and allow swipe left/right.</td>
</tr>
</tbody>
</table>

View File

@ -38,7 +38,7 @@ bootstrap/
└── bootstrap.min.js.map
{% endhighlight %}
This is the most basic form of Bootstrap: precompiled files for quick drop-in usage in nearly any web project. We provide compiled CSS and JS (`bootstrap.*`), as well as compiled and minified CSS and JS (`bootstrap.min.*`). [source maps](https://developers.google.com/web/tools/chrome-devtools/javascript/source-maps) (`bootstrap.*.map`) are available for use with certain browsers' developer tools. Bundled JS files (`bootstrap.bundle.js` and minified `bootstrap.bundle.min.js`) include [Popper](https://popper.js.org/), but not [jQuery](https://jquery.com/).
This is the most basic form of Bootstrap: precompiled files for quick drop-in usage in nearly any web project. We provide compiled CSS and JS (`bootstrap.*`), as well as compiled and minified CSS and JS (`bootstrap.min.*`). [source maps](https://developers.google.com/web/tools/chrome-devtools/javascript/source-maps) (`bootstrap.*.map`) are available for use with certain browsers' developer tools. Bundled JS files (`bootstrap.bundle.js` and minified `bootstrap.bundle.min.js`) include [Popper](https://popper.js.org/) and [HammerJS](https://hammerjs.github.io/), but not [jQuery](https://jquery.com/).
## CSS files

View File

@ -37,7 +37,7 @@ We use [jQuery's slim build](https://blog.jquery.com/2016/06/09/jquery-3-0-final
Curious which components explicitly require jQuery, our JS, and Popper.js? Click the show components link below. If you're at all unsure about the general page structure, keep reading for an example page template.
Our `bootstrap.bundle.js` and `bootstrap.bundle.min.js` include [Popper](https://popper.js.org/), but not [jQuery](https://jquery.com/). For more information about what's included in Bootstrap, please see our [contents]({{ site.baseurl }}/docs/{{ site.docs_version }}/getting-started/contents/#precompiled-bootstrap) section.
Our `bootstrap.bundle.js` and `bootstrap.bundle.min.js` include [Popper](https://popper.js.org/) and [HammerJS](https://hammerjs.github.io/), but not [jQuery](https://jquery.com/). For more information about what's included in Bootstrap, please see our [contents]({{ site.baseurl }}/docs/{{ site.docs_version }}/getting-started/contents/#precompiled-bootstrap) section.
<details>
<summary class="text-primary mb-3">Show components requiring JavaScript</summary>