├── .gitignore
├── demo
├── .babelrc
├── postcss.config.js
├── src
│ ├── index.js
│ └── styles
│ │ └── main.css
├── webpack.config.js
├── static
│ ├── index.html
│ └── main.css
└── package.json
├── package.json
├── README.md
└── index.js
/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 | example
3 |
--------------------------------------------------------------------------------
/demo/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | "transform-class-properties",
4 | "transform-object-rest-spread",
5 | ['module-resolver', {
6 | 'root': ['.'],
7 | 'alias': {}
8 | }]
9 | ],
10 | "presets": [
11 | "es2015",
12 | "react"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/demo/postcss.config.js:
--------------------------------------------------------------------------------
1 | const p = process.env.NODE_ENV === 'production'
2 |
3 | module.exports = {
4 | plugins: [
5 | require('postcss-import'),
6 | require('postcss-nested'),
7 | require('postcss-cssnext'),
8 | require('postcss-discard-comments'),
9 | require('postcss-reporter'),
10 | p ? require('cssnano') : ''
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/demo/src/index.js:
--------------------------------------------------------------------------------
1 | /** imports */
2 | import rosin from '../../index.js'
3 |
4 | /** hot module reloading */
5 | if (module.hot && process && process.env.NODE_ENV !== 'production') {
6 | module.hot.accept()
7 | }
8 |
9 | /** application code */
10 | const root = document.getElementById('root')
11 |
12 | const swiper = rosin(root)
13 |
14 | swiper.on('drag', arg => {
15 | console.log(arg)
16 | root.innerHTML = `
${JSON.stringify(arg, null, ' ')}`
17 | })
18 |
--------------------------------------------------------------------------------
/demo/src/styles/main.css:
--------------------------------------------------------------------------------
1 | @import 'svbstrate/src/lib/reset';
2 | @import 'svbstrate/src/lib/display';
3 | @import 'svbstrate/src/lib/positioning';
4 | @import 'svbstrate/src/lib/flexbox';
5 | @import 'svbstrate/src/lib/align';
6 | @import 'svbstrate/src/lib/spacing';
7 | @import 'svbstrate/src/lib/buttons';
8 | @import 'svbstrate/src/lib/forms';
9 | @import 'svbstrate/src/lib/lists';
10 | @import 'svbstrate/src/lib/typography';
11 | @import 'svbstrate/src/lib/z-index';
12 |
13 | body {
14 | height: 80vh;
15 | }
16 |
17 | #root {
18 | width: 50vh;
19 | height: 50vh;
20 | position: absolute;
21 | margin: auto;
22 | top: 0;
23 | bottom: 0;
24 | left: 0;
25 | right: 0;
26 | background: tomato;
27 | }
28 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rosin",
3 | "version": "2.0.2",
4 | "description": "Touch detection library",
5 | "source": "index.js",
6 | "module": "dist/rosin.es.js",
7 | "main": "dist/rosin.js",
8 | "umd:main": "dist/rosin.umd.js",
9 | "files": [
10 | "dist"
11 | ],
12 | "scripts": {
13 | "build": "microbundle build",
14 | "watch": "microbundle watch"
15 | },
16 | "repository": {
17 | "type": "git",
18 | "url": "git+ssh://git@github.com/estrattonbailey/rosin.git"
19 | },
20 | "author": "estrattonbailey",
21 | "license": "MIT",
22 | "bugs": {
23 | "url": "https://github.com/estrattonbailey/rosin/issues"
24 | },
25 | "homepage": "https://github.com/estrattonbailey/rosin#readme",
26 | "devDependencies": {
27 | "microbundle": "^0.4.4"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/demo/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack')
2 | const path = require('path')
3 | const p = process.env.NODE_ENV === 'production'
4 |
5 | module.exports = {
6 | devtool: 'cheap-module-source-map',
7 | entry: path.join(__dirname, 'src/index.js'),
8 | output: {
9 | path: path.join(__dirname, 'static'),
10 | filename: 'index.js',
11 | publicPath: '/'
12 | },
13 | module: {
14 | rules: [
15 | {
16 | test: /\.js$/,
17 | exclude: /node_modules/,
18 | include: path.join(__dirname, 'src'),
19 | loaders: ['babel-loader']
20 | },
21 | ]
22 | },
23 | resolve: {
24 | alias: {
25 | '@': path.join(__dirname, 'src')
26 | }
27 | },
28 | plugins: p ? [] : [
29 | new webpack.HotModuleReplacementPlugin()
30 | ],
31 | devServer: {
32 | hot: true,
33 | contentBase: path.join(__dirname, 'static'),
34 | publicPath: '/',
35 | compress: true
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/demo/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | @estrattonbailey/frame
29 |
30 |
31 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frame",
3 | "version": "0.0.1",
4 | "description": "",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "start": "concurrently 'npm run watch:css' 'npm run serve'",
8 | "serve": "webpack-dev-server --open",
9 | "build": "npm run build:js && npm run build:css",
10 | "build:js": "NODE_ENV=production webpack --progress -p",
11 | "build:css": "NODE_ENV=production postcss src/styles/main.css -o static/main.css",
12 | "watch:css": "NODE_ENV=development postcss src/styles/main.css -w -o static/main.css"
13 | },
14 | "author": "estrattonbailey",
15 | "license": "MIT",
16 | "devDependencies": {
17 | "babel-core": "^6.25.0",
18 | "babel-eslint": "^7.2.3",
19 | "babel-loader": "^7.0.0",
20 | "babel-plugin-module-resolver": "^2.7.1",
21 | "babel-plugin-transform-class-properties": "^6.24.1",
22 | "babel-plugin-transform-object-assign": "^6.22.0",
23 | "babel-plugin-transform-object-rest-spread": "^6.23.0",
24 | "babel-preset-es2015": "^6.24.1",
25 | "babel-preset-react": "^6.24.1",
26 | "concurrently": "^3.6.1",
27 | "cssnano": "^3.10.0",
28 | "postcss": "^6.0.23",
29 | "postcss-cli": "^4.1.1",
30 | "postcss-cssnext": "^2.11.0",
31 | "postcss-discard-comments": "^2.0.4",
32 | "postcss-import": "^10.0.0",
33 | "postcss-nested": "^2.0.2",
34 | "standard": "^10.0.2",
35 | "standard-loader": "^6.0.1",
36 | "webpack": "^2.6.1",
37 | "webpack-dev-server": "^2.11.5"
38 | },
39 | "dependencies": {
40 | "svbstrate": "^4.1.1"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # rosin
2 |
3 | A tiny single-finger tap and swipe library. Works with touch and/or mouse
4 | events. **700 bytes gzipped.**
5 |
6 | ## Install
7 |
8 | ```
9 | npm i rosin --save
10 | ```
11 |
12 | # Usage
13 |
14 | ```javascript
15 | import rosin from "rosin";
16 |
17 | const swiper = rosin(document.body);
18 | ```
19 |
20 | Emitted values are _relative_ to the DOM node you instantiated on.
21 |
22 | ```javascript
23 | swiper.on("tap", ({ x, y }, e) => {});
24 | swiper.on("mouseup", ({ x, y }, e) => {});
25 | swiper.on("mousedown", ({ x, y }, e) => {});
26 |
27 | /** Fired once on each swipe */
28 | swiper.on("left", ({ x, y }, e) => {});
29 | swiper.on("right", ({ x, y }, e) => {});
30 | swiper.on("up", ({ x, y }, e) => {});
31 | swiper.on("down", ({ x, y }, e) => {});
32 | ```
33 |
34 | Drag events emit a different payload. It looks like this:
35 |
36 | ```
37 | {
38 | ix, // initial X coordinate
39 | iy, // initial Y coordinate
40 | dx, // delta (change) in X coordinate
41 | dy, // delta (change) in Y coordinate
42 | x, // current X coordinate
43 | y, // current Y coordinate
44 | }
45 | ```
46 |
47 | ```javascript
48 | /** Fired on every animation frame */
49 | swiper.on("drag", (coords, e) => {});
50 | swiper.on("dragLeft", (coords, e) => {});
51 | swiper.on("dragRight", (coords, e) => {});
52 | swiper.on("dragUp", (coords, e) => {});
53 | swiper.on("dragDown", (coords, e) => {});
54 | ```
55 |
56 | Each emitter also returns a function to destroy itself:
57 |
58 | ```javascript
59 | const tapListener = swiper.on("tap", () => {});
60 |
61 | tapListener(); // destroy listener
62 | ```
63 |
64 | To destroy the entire instance:
65 |
66 | ```javascript
67 | swiper.destroy();
68 | ```
69 |
70 | ## License
71 |
72 | MIT License © [Eric Bailey](https://estrattonbailey.com)
73 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const abs = Math.abs
2 |
3 | function coord (e, y) {
4 | return e.touches ? e.touches[0][y ? 'pageY' : 'pageX'] : e[y ? 'clientY' : 'clientX']
5 | }
6 |
7 | function coords (ctx, e) {
8 | const { top, bottom, left, right } = ctx.getBoundingClientRect()
9 | const x = coord(e) - left
10 | const y = coord(e, 1) - top
11 |
12 | return {
13 | x,
14 | y,
15 | px: x / (right - left),
16 | py: y / (bottom - top)
17 | }
18 | }
19 |
20 | export default function rosin (ctx) {
21 | let dir
22 | let x
23 | let y
24 | let focus = false
25 | let dragging = false
26 |
27 | const fns = {}
28 |
29 | function start (e) {
30 | if (e.target === ctx || ctx.contains(e.target)) {
31 | focus = true
32 |
33 | const pos = coords(ctx, e)
34 |
35 | x = pos.x
36 | y = pos.y
37 |
38 | emit('start', { x, y }, e)
39 | }
40 | }
41 |
42 | function end (e) {
43 | if (focus) {
44 | emit('end', e)
45 |
46 | if (dragging) {
47 | emit('dragEnd', e)
48 | } else {
49 | emit('tap', { x, y }, e)
50 | }
51 | }
52 |
53 | cancel()
54 | }
55 |
56 | function move (e) {
57 | if (focus) {
58 | const cancel = e.preventDefault.bind(e)
59 | const pos = coords(ctx, e)
60 | const deltaX = pos.x - x
61 | const deltaY = pos.y - y
62 | const travelX = abs(deltaX)
63 | const travelY = abs(deltaY)
64 | const horizontal = travelX > travelY
65 |
66 | if (travelX < 10 && travelY < 10) return
67 |
68 | const payload = {
69 | px: pos.px,
70 | py: pos.py,
71 | ix: x,
72 | iy: y,
73 | dx: deltaX,
74 | dy: deltaY,
75 | x: x + deltaX,
76 | y: y + deltaY,
77 | }
78 |
79 | dragging = true
80 |
81 | fns.drag && cancel()
82 |
83 | emit('drag', payload, e)
84 |
85 | if (deltaX > 0 && horizontal) {
86 | fns.dragRight && cancel()
87 |
88 | emit('dragRight', payload, e)
89 |
90 | if (dir !== 'right') {
91 | emit('right', { x, y }, e)
92 | dir = 'right'
93 | }
94 | } else if (deltaX < 0 && horizontal) {
95 | fns.dragLeft && cancel()
96 |
97 | emit('dragLeft', payload, e)
98 |
99 | if (dir !== 'left') {
100 | emit('left', { x, y }, e)
101 | dir = 'left'
102 | }
103 | } else if (deltaY > 0 && !horizontal) {
104 | fns.dragDown && cancel()
105 |
106 | emit('dragDown', payload, e)
107 |
108 | if (dir !== 'down') {
109 | emit('down', { x, y }, e)
110 | dir = 'down'
111 | }
112 | } else if (deltaY < 0 && !horizontal) {
113 | fns.dragUp && cancel()
114 |
115 | emit('dragUp', payload, e)
116 |
117 | if (dir !== 'up') {
118 | emit('up', { x, y }, e)
119 | dir = 'up'
120 | }
121 | }
122 | }
123 | }
124 |
125 | function emit (type, payload, event) {
126 | if (fns[type]) for (let i = 0; i < fns[type].length; i++) fns[type][i](payload, event)
127 | }
128 |
129 | function cancel () {
130 | dir = null
131 | x = null
132 | y = null
133 | focus = false
134 | }
135 |
136 | window.addEventListener('mousedown', start)
137 | window.addEventListener('touchstart', start)
138 |
139 | window.addEventListener('mouseup', end)
140 | window.addEventListener('touchend', end)
141 |
142 | window.addEventListener('mousemove', move)
143 | window.addEventListener('touchmove', move)
144 |
145 | window.addEventListener('touchcancel', cancel)
146 |
147 | return {
148 | on (type, fn) {
149 | fns[type] = fns[type] || []
150 | fns[type].indexOf(fn) < 0 && fns[type].push(fn)
151 | return () => fns[type].splice(fns[type].indexOf(fn), 1)
152 | },
153 | destroy () {
154 | window.removeEventListener('mousedown', start)
155 | window.removeEventListener('touchstart', start)
156 |
157 | window.removeEventListener('mouseup', end)
158 | window.removeEventListener('touchend', end)
159 |
160 | window.removeEventListener('mousemove', move)
161 | window.removeEventListener('touchmove', move)
162 |
163 | window.removeEventListener('touchcancel', cancel)
164 | }
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/demo/static/main.css:
--------------------------------------------------------------------------------
1 | html, body{ margin:0 }
2 | *,
3 | *::before,
4 | *::after{
5 | box-sizing:border-box;
6 | }
7 | a,
8 | button,
9 | [role="button"],
10 | input,
11 | label,
12 | select,
13 | textarea{
14 | -ms-touch-action:manipulation;
15 | touch-action:manipulation;
16 | }
17 | .block{ display:block }
18 | .inline-block{ display:inline-block }
19 | .rel{ position:relative }
20 | .abs{ position:absolute }
21 | .fix{ position:fixed }
22 | .fill, .top{ top:0 }
23 | .fill, .bottom{ bottom:0 }
24 | .fill, .left{ left:0 }
25 | .fill, .right{ right:0 }
26 | .x{ width:100% }
27 | .y{ height:100% }
28 | .f{ display:-ms-flexbox; display:flex }
29 | .fw{ -ms-flex-wrap:wrap; flex-wrap:wrap }
30 | .ais{ -ms-flex-align:start; align-items:flex-start }
31 | .aie{ -ms-flex-align:end; align-items:flex-end }
32 | .aic{ -ms-flex-align:center; align-items:center }
33 | .aib{ -ms-flex-align:baseline; align-items:baseline }
34 | .jcs{ -ms-flex-pack:start; justify-content:flex-start }
35 | .jce{ -ms-flex-pack:end; justify-content:flex-end }
36 | .jcc{ -ms-flex-pack:center; justify-content:center }
37 | .jcb{ -ms-flex-pack:justify; justify-content:space-between }
38 | .fa{
39 | -ms-flex:1 1 auto;
40 | flex:1 1 auto;
41 | min-width:0;
42 | min-height:0;
43 | }
44 | .al{ text-align:left }
45 | .ac{ text-align:center }
46 | .ar{ text-align:right }
47 | .aj{ text-align:justify }
48 | .mxa, .ma{ margin-left:auto }
49 | .mxa, .ma{ margin-right:auto }
50 | .mya, .ma{ margin-top:auto }
51 | .mya, .ma{ margin-bottom:auto; }
52 | .pt0, .py0, .p0{ padding-top:0 }
53 | .pb0, .py0, .p0{ padding-bottom:0 }
54 | .mt0, .my0, .m0{ margin-top:0 }
55 | .mb0, .my0, .m0{ margin-bottom:0 }
56 | .mt025, .my025, .m025{ margin-top:0.25em }
57 | .mb025, .my025, .m025{ margin-bottom:0.25em }
58 | .ml025, .mx025, .m025{ margin-left:0.25em }
59 | .mr025, .mx025, .m025{ margin-right:0.25em }
60 | .pt025, .py025, .p025{ padding-top:0.25em }
61 | .pb025, .py025, .p025{ padding-bottom:0.25em }
62 | .pl025, .px025, .p025{ padding-left:0.25em }
63 | .pr025, .px025, .p025{ padding-right:0.25em }
64 | .mt05, .my05, .m05{ margin-top:0.5em }
65 | .mb05, .my05, .m05{ margin-bottom:0.5em }
66 | .ml05, .mx05, .m05{ margin-left:0.5em }
67 | .mr05, .mx05, .m05{ margin-right:0.5em }
68 | .pt05, .py05, .p05{ padding-top:0.5em }
69 | .pb05, .py05, .p05{ padding-bottom:0.5em }
70 | .pl05, .px05, .p05{ padding-left:0.5em }
71 | .pr05, .px05, .p05{ padding-right:0.5em }
72 | .mt075, .my075, .m075{ margin-top:0.75em }
73 | .mb075, .my075, .m075{ margin-bottom:0.75em }
74 | .ml075, .mx075, .m075{ margin-left:0.75em }
75 | .mr075, .mx075, .m075{ margin-right:0.75em }
76 | .pt075, .py075, .p075{ padding-top:0.75em }
77 | .pb075, .py075, .p075{ padding-bottom:0.75em }
78 | .pl075, .px075, .p075{ padding-left:0.75em }
79 | .pr075, .px075, .p075{ padding-right:0.75em }
80 | .mt1, .my1, .m1{ margin-top:1em }
81 | .mb1, .my1, .m1{ margin-bottom:1em }
82 | .ml1, .mx1, .m1{ margin-left:1em }
83 | .mr1, .mx1, .m1{ margin-right:1em }
84 | .pt1, .py1, .p1{ padding-top:1em }
85 | .pb1, .py1, .p1{ padding-bottom:1em }
86 | .pl1, .px1, .p1{ padding-left:1em }
87 | .pr1, .px1, .p1{ padding-right:1em }
88 | button{
89 | border:0;
90 | border-radius:0;
91 | display:inline-block;
92 | cursor:pointer;
93 | -webkit-appearance:none;
94 | }
95 | button.button,
96 | .button[role="button"],
97 | input.button[type="submit"]{
98 | background-color:#000;
99 | color:white;
100 | padding:0.5em 1.5em;
101 | }
102 | form{
103 | margin:0;
104 | }
105 | input,
106 | textarea,
107 | select{
108 | display:inline-block;
109 | outline:0;
110 | border-radius:0;
111 | border:1px solid #000;
112 | position:relative;
113 | font-size:inherit;
114 | background-color:transparent;
115 | }
116 | textarea{
117 | max-width:100%;
118 | overflow:auto;
119 | resize:vertical;
120 | }
121 | ol, ul{
122 | list-style:none;
123 | padding:0;
124 | margin:0;
125 | }
126 | ul.list,
127 | ol.list{
128 | padding-left:2em;
129 | }
130 | ol.list{
131 | list-style:decimal;
132 | }
133 | ul.list{
134 | list-style:disc;
135 | }
136 | html, body{
137 | color:#000;
138 | font-family:-apple-system, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, BlinkMacSystemFont, sans-serif;
139 | font-size:100%;
140 | line-height:1.7;
141 | font-weight:400;
142 | -webkit-font-smoothing:antialiased;
143 | }
144 | h1, h2, h3, h4, h5, h6{
145 | margin:0;
146 | }
147 | .s1,
148 | h1, .h1{
149 | font-size:64px;
150 | font-size:4rem;
151 | }
152 | h1, .h1{
153 | line-height:1.1;
154 | }
155 | .s2,
156 | h2, .h2{
157 | font-size:48px;
158 | font-size:3rem;
159 | }
160 | h2, .h2{
161 | line-height:1.3;
162 | }
163 | .s3,
164 | h3, .h3{
165 | font-size:32px;
166 | font-size:2rem;
167 | }
168 | h3, .h3{
169 | line-height:1.5;
170 | }
171 | .s4,
172 | h4, .h4{
173 | font-size:24px;
174 | font-size:1.5rem;
175 | }
176 | h4, .h4{
177 | line-height:1.5;
178 | }
179 | .s5,
180 | h5, .h5{
181 | font-size:16px;
182 | font-size:1rem;
183 | }
184 | h5, .h5{
185 | line-height:1.6;
186 | }
187 | .s6,
188 | h6, .h6{
189 | font-size:14px;
190 | font-size:0.875rem;
191 | }
192 | h6, .h6{
193 | line-height:1.6;
194 | }
195 | .s0,
196 | p, .p{
197 | font-size:16px;
198 | font-size:1rem;
199 | }
200 | p, .p{
201 | line-height:1.7;
202 | }
203 | p{
204 | margin:1em 0;
205 | }
206 | a{
207 | color:inherit;
208 | }
209 | hr{
210 | display:block;
211 | border:0;
212 | margin:0;
213 | height:1px;
214 | width:100%;
215 | background-color:currentColor;
216 | color:inherit;
217 | }
218 | small, .small{
219 | font-size:0.75em;
220 | }
221 | strong, .b{
222 | font-weight:bold;
223 | }
224 | em, .i{
225 | font-style:italic;
226 | }
227 | .caps{
228 | text-transform:uppercase;
229 | }
230 | .no-under{
231 | text-decoration:none;
232 | }
233 | .z0{ z-index:0 }
234 | .z1{ z-index:100 }
235 | .z2{ z-index:200 }
236 | .z3{ z-index:300 }
237 | .z4{ z-index:400 }
238 | .z5{ z-index:500 }
239 | .z6{ z-index:600 }
240 | .z7{ z-index:700 }
241 | .z8{ z-index:800 }
242 | .z9{ z-index:900 }
243 | .z10{ z-index:1000 }
244 | body{
245 | height:80vh;
246 | }
247 | #root{
248 | width:50vh;
249 | height:50vh;
250 | position:absolute;
251 | margin:auto;
252 | top:0;
253 | bottom:0;
254 | left:0;
255 | right:0;
256 | background:tomato;
257 | }
258 |
--------------------------------------------------------------------------------