├── .npmignore ├── src ├── index.js ├── utils.js ├── displace.d.ts ├── displace.js └── events.js ├── docs ├── fonts │ ├── ico.ttf │ ├── ico.woff │ └── ico.svg ├── js │ ├── main.js │ └── demos │ │ ├── magnifier.js │ │ ├── custom-move.js │ │ ├── sorting.js │ │ └── intro.js ├── scss │ ├── basic │ │ ├── _variables.scss │ │ ├── _fonts.scss │ │ ├── _global.scss │ │ └── _normalize.scss │ ├── main │ │ ├── _custom-displace-fn.scss │ │ ├── _magnifier-demo.scss │ │ ├── _intro.scss │ │ ├── _basic-demo.scss │ │ └── _sorting-demo.scss │ └── style.scss ├── style.css └── index.html ├── .gitignore ├── webpack.config.docs.js ├── LICENSE.md ├── webpack.config.build.js ├── .eslintrc ├── package.json ├── README.md └── dist ├── displace.min.js └── displace.js /.npmignore: -------------------------------------------------------------------------------- 1 | docs 2 | node_modules 3 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import displace from 'displace'; 2 | 3 | module.exports = displace; -------------------------------------------------------------------------------- /docs/fonts/ico.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catc/displace/HEAD/docs/fonts/ico.ttf -------------------------------------------------------------------------------- /docs/fonts/ico.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catc/displace/HEAD/docs/fonts/ico.woff -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Node stuff. 2 | node_modules/ 3 | npm-debug.log 4 | 5 | # Folder view config. 6 | .DS_Store 7 | 8 | # Thumbnail caches. 9 | ._* 10 | Thumbs.db 11 | 12 | ## TEMP 13 | testing/ 14 | -------------------------------------------------------------------------------- /docs/js/main.js: -------------------------------------------------------------------------------- 1 | import highlight from 'highlight.js'; 2 | 3 | // run demos 4 | import './demos/intro'; 5 | import './demos/magnifier'; 6 | import './demos/sorting'; 7 | import './demos/custom-move'; 8 | 9 | // init all highlighted code areas 10 | highlight.initHighlightingOnLoad(); 11 | -------------------------------------------------------------------------------- /docs/scss/basic/_variables.scss: -------------------------------------------------------------------------------- 1 | 2 | // fonts 3 | // $font-main: 'Hind Vadodara', sans-serif; 4 | $font-main: 'Montserrat', sans-serif; 5 | $font-code: 'Oxygen Mono', monospace; 6 | 7 | // colors 8 | $b1: #080808; 9 | $b2: #1A1A1A; 10 | $b3: #23241f; 11 | $b4: #3c3c3c; 12 | $b5: #404040; 13 | $b6: #696969; 14 | 15 | $c1: #ffc107; 16 | $c2: #ff9800; 17 | $c3: #ff5722; 18 | $c4: #F27E00; -------------------------------------------------------------------------------- /docs/scss/main/_custom-displace-fn.scss: -------------------------------------------------------------------------------- 1 | 2 | 3 | .custom-fn { 4 | &__wrapper { 5 | width: 600px - 1; 6 | height: 400px - 1; 7 | border: 1px solid $b2; 8 | position: relative; 9 | } 10 | &__div { 11 | $size: 99px; 12 | display: inline-block; 13 | width: $size; 14 | height: $size; 15 | background: $c1; 16 | cursor: move; 17 | top: $size + 1; 18 | left: $size + 1; 19 | } 20 | &__line { 21 | background: rgba($b6, 0.8); 22 | display: inline-block; 23 | position: absolute; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | 2 | export function generateClamp(min, max){ 3 | return function(val){ 4 | return Math.min(Math.max(val, min), max); 5 | }; 6 | } 7 | 8 | export function isRelative(el) { 9 | return window.getComputedStyle(el).position === 'relative'; 10 | } 11 | 12 | export function generateMoveFn(){ 13 | if (window.requestAnimationFrame) { 14 | return function(customFn){ 15 | const move = customFn || defaultMove; 16 | 17 | return function (el, x, y){ 18 | window.requestAnimationFrame(function () { 19 | move(el, x, y); 20 | }); 21 | }; 22 | }; 23 | } 24 | return function (customFn) { 25 | return function (el, x, y) { 26 | const move = customFn || defaultMove; 27 | move(el, x, y); 28 | }; 29 | }; 30 | } 31 | 32 | function defaultMove(el, x, y) { 33 | el.style.left = x + 'px'; 34 | el.style.top = y + 'px'; 35 | } 36 | -------------------------------------------------------------------------------- /webpack.config.docs.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | 4 | module.exports = [ 5 | { 6 | entry: './docs/js/main.js', 7 | // entry: './gh-page/js/main.js', 8 | 9 | // resolves paths similar to NODE_PATH=. 10 | // resolve: {root: __dirname + '/' }, 11 | resolve: { 12 | root: [ 13 | __dirname + '/', 14 | ] 15 | }, 16 | output: { 17 | path: path.resolve(__dirname, 'docs'), 18 | publicPath: '/', 19 | filename: 'bundle.js' // can change to [name].js if multiple entry points 20 | }, 21 | module: { 22 | loaders: [ 23 | { 24 | test: /\.js$/, 25 | exclude: /node_modules/, 26 | loader: 'babel-loader' 27 | } 28 | ] 29 | }, 30 | plugins: [ 31 | new webpack.optimize.UglifyJsPlugin({ 32 | compress: { 33 | warnings: false, 34 | }, 35 | output: { 36 | comments: false 37 | } 38 | }) 39 | ], 40 | } 41 | ]; 42 | -------------------------------------------------------------------------------- /docs/scss/main/_magnifier-demo.scss: -------------------------------------------------------------------------------- 1 | 2 | .magnifier-demo { 3 | width: 600px; 4 | &__image-wrapper { 5 | width: 400px; 6 | position: relative; 7 | display: inline-block; 8 | } 9 | 10 | img { 11 | height: auto; 12 | max-width: 100%; 13 | vertical-align: middle; 14 | display: block; 15 | } 16 | 17 | &__zoomer { 18 | display: inline-block; 19 | position: absolute; 20 | left: 245px; 21 | top: 102px; 22 | background: rgba(255,255,255,0.4); 23 | cursor: move; 24 | border: 1px solid rgba(255, 255, 255, 0.7); 25 | } 26 | 27 | &__zoom-preview { 28 | display: inline-block; 29 | width: 200px; 30 | float: right; 31 | position: relative; 32 | height: 100%; 33 | 34 | border-left: 3px solid #f7f7f7; 35 | box-sizing: border-box; 36 | } 37 | 38 | a { 39 | color: $c4; 40 | text-decoration: none; 41 | } 42 | &__code-link { 43 | color: #0b97ed !important; 44 | } 45 | &__photo-ref { 46 | display: inline-block; 47 | float: right; 48 | } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /docs/scss/basic/_fonts.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'ico'; 3 | src: 4 | url('./fonts/ico.ttf?ib1ojd') format('truetype'), 5 | url('./fonts/ico.woff?ib1ojd') format('woff'), 6 | url('./fonts/ico.svg?ib1ojd#ico') format('svg'); 7 | font-weight: normal; 8 | font-style: normal; 9 | } 10 | 11 | [class^="i-"], [class*=" i-"], %ico { 12 | /* use !important to prevent issues with browser extensions that change fonts */ 13 | font-family: 'ico' !important; 14 | speak: none; 15 | font-style: normal; 16 | font-weight: normal; 17 | font-variant: normal; 18 | text-transform: none; 19 | line-height: 1; 20 | font-size: 2rem; 21 | 22 | /* Better Font Rendering =========== */ 23 | -webkit-font-smoothing: antialiased; 24 | -moz-osx-font-smoothing: grayscale; 25 | } 26 | 27 | .i-check:before { 28 | content: "\e904"; 29 | } 30 | .i-download:before { 31 | content: "\e900"; 32 | } 33 | .i-info-with-circle:before { 34 | content: "\e62c"; 35 | } 36 | .i-info2:before { 37 | content: "\e62d"; 38 | } 39 | .i-power-plug:before { 40 | content: "\e901"; 41 | } 42 | .i-github:before { 43 | content: "\e902"; 44 | } 45 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Catalin Covic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/scss/main/_intro.scss: -------------------------------------------------------------------------------- 1 | 2 | .intro { 3 | background: #ffd044; 4 | border-bottom: 1px solid #FFC107; 5 | padding: 7em 4em 4em; 6 | margin-bottom: 7em; 7 | 8 | &__description { 9 | font-size: 1.7rem; 10 | color: $b5; 11 | } 12 | 13 | &__table-of-contents { 14 | margin-top: 2em; 15 | a { 16 | display: inline-block; 17 | font-size: 1.5rem; 18 | padding: 14px 20px 13px; 19 | text-decoration: none; 20 | border-radius: 4px; 21 | margin-right: 1em; 22 | transition: 0.2s ease-out; 23 | 24 | background: $b4; 25 | color: white; 26 | 27 | span { 28 | display: inline-block; 29 | margin-right: 8px; 30 | vertical-align: -3px; 31 | &:before { 32 | color: inherit; 33 | } 34 | } 35 | &:hover { 36 | color: $c1; 37 | } 38 | } 39 | } 40 | 41 | &__info { 42 | list-style: none; 43 | counter-reset: list; 44 | padding-left: 1em; 45 | li { 46 | margin: 6px 0; 47 | &:before { 48 | @extend %ico; 49 | content: "\e904"; 50 | font-size: 1.6rem; 51 | display: inline-block; 52 | vertical-align: -2px; 53 | margin-right: 10px; 54 | color: $c4; 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /docs/scss/basic/_global.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | html { 4 | font-size: 62.5%; 5 | overflow-y: scroll; 6 | -webkit-text-size-adjust: 100%; 7 | -ms-text-size-adjust: 100%; 8 | } 9 | 10 | body { 11 | background-color: #f7f7f7; 12 | color: $b5; 13 | font-family: $font-main; 14 | font-size: 1.4em; 15 | font-style: normal; 16 | font-weight: 400; 17 | line-height: 1.55; 18 | position: relative; 19 | -webkit-font-smoothing: antialiased; 20 | font-smoothing: antialiased; 21 | } 22 | 23 | h1,h2,h3,h4,h5,h6 { 24 | text-rendering: optimizelegibility; 25 | -webkit-font-smoothing: antialiased; 26 | } 27 | 28 | a { 29 | color: inherit; 30 | text-decoration: underline; 31 | } 32 | 33 | code { 34 | font-family: $font-code; 35 | } 36 | 37 | button { 38 | border: none; 39 | background:none; 40 | box-shadow: none; 41 | text-shadow: none; 42 | -webkit-appearance: none; 43 | -moz-appearance: none; 44 | font-family: $font-main; 45 | &:not(:disabled){ 46 | cursor: pointer; 47 | } 48 | &:hover, &:focus, &:active { 49 | // border: none; 50 | // background:none; 51 | outline: none; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /webpack.config.build.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const pack = require('package'); 3 | 4 | const banner = `${pack.name}.js ${pack.version} - ${pack.description} 5 | Copyright (c) ${new Date().getFullYear()} ${pack.author} - ${pack.homepage} 6 | License: ${pack.license}`; 7 | 8 | const PROD = process.env.NODE_ENV === 'prod'; 9 | 10 | const plugins = [ 11 | new webpack.BannerPlugin(banner) 12 | ]; 13 | if (PROD){ 14 | plugins.unshift(new webpack.optimize.UglifyJsPlugin({ 15 | compress: { 16 | warnings: false, 17 | }, 18 | output: { 19 | comments: false 20 | } 21 | })); 22 | } 23 | 24 | new webpack.optimize.UglifyJsPlugin({ 25 | compress: { 26 | warnings: false, 27 | }, 28 | output: { 29 | comments: false 30 | } 31 | }); 32 | 33 | module.exports = { 34 | context: __dirname + '/src', 35 | entry: './index.js', 36 | output: { 37 | path: __dirname + '/dist', 38 | filename: `displace${PROD ? '.min' : ''}.js`, 39 | library: `${pack.name}`, 40 | libraryTarget: 'umd' 41 | }, 42 | resolve: { 43 | root: __dirname + '/src' 44 | }, 45 | module: { 46 | loaders: [{ 47 | test: /\.js$/, 48 | exclude: /node_modules/, 49 | loader: 'babel-loader' 50 | }] 51 | }, 52 | plugins: plugins 53 | }; -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | }, 5 | "parser": "babel-eslint", 6 | "parserOptions": { 7 | "ecmaVersion": 6, 8 | "sourceType": "module", 9 | "ecmaFeatures": { 10 | "blockBindings": true, 11 | "generators": true, 12 | "arrowFunctions": true, 13 | "destructuring": true, 14 | "defaultParams": true, 15 | "restParams": true, 16 | "spread": true, 17 | "templateStrings": true, 18 | "modules": true, 19 | "arrow-body-style": true, 20 | "jsx": true, 21 | "objectLiteralShorthandMethods": true 22 | } 23 | }, 24 | "rules": { 25 | "curly": 2, 26 | "semi": 1, 27 | "eqeqeq": 1, 28 | "no-eval": 2, 29 | "no-unused-vars": 1, 30 | "no-use-before-define": [1, "nofunc"], 31 | "no-undef": 1, 32 | "global-require": 0, 33 | "quotes": [1, "single", "avoid-escape"], 34 | "indent": [2, "tab", {"SwitchCase": 1}], 35 | "no-mixed-spaces-and-tabs": 2, 36 | "no-trailing-spaces": [2, { "skipBlankLines": true }], 37 | "arrow-parens": [1, "as-needed"], 38 | "keyword-spacing": [1, {"before": true, "after": true}], 39 | "no-var": 1 40 | }, 41 | "globals": { 42 | "_": true, 43 | "$", 44 | "console": true, 45 | "window": true, 46 | "document", 47 | } 48 | } -------------------------------------------------------------------------------- /docs/js/demos/magnifier.js: -------------------------------------------------------------------------------- 1 | import displace from 'dist/displace.min.js'; 2 | 3 | const img = document.querySelector('#magnifier-img'); 4 | const preview = document.querySelector('.magnifier-demo__zoom-preview'); 5 | const zoomer = document.querySelector('.magnifier-demo__zoomer'); 6 | const imgDim = {}; 7 | 8 | if (img.complete) { 9 | setupMagnifier(); 10 | } else { 11 | img.addEventListener('load', setupMagnifier); 12 | } 13 | 14 | function setupMagnifier() { 15 | // set preview bg 16 | preview.style.background = `url('${img.src}') no-repeat center center`; 17 | 18 | // set preview height 19 | preview.style.height = img.offsetHeight + 'px'; 20 | 21 | const previewR = preview.offsetWidth / preview.offsetHeight; 22 | 23 | // set zoomer dimensions 24 | const zoomerW = 50; 25 | zoomer.style.width = zoomerW + 'px'; 26 | zoomer.style.height = zoomerW / previewR + 'px'; 27 | 28 | // set bg size for preview div 29 | const sizeRatio = img.offsetWidth / preview.offsetWidth; 30 | preview.style.backgroundSize = `${img.naturalWidth / sizeRatio}px ${img.naturalHeight / sizeRatio}px`; 31 | 32 | // cache dimensions 33 | imgDim.w = img.offsetWidth; 34 | imgDim.h = img.offsetHeight; 35 | 36 | // init displace 37 | displace(zoomer, { 38 | constrain: true, 39 | onMouseMove: updatePreview, 40 | onTouchMove: updatePreview 41 | }); 42 | 43 | // update preview 44 | updatePreview(zoomer); 45 | } 46 | 47 | function updatePreview(el) { 48 | const x = el.offsetLeft / (imgDim.w - el.offsetWidth) * 100; 49 | const y = el.offsetTop / (imgDim.h - el.offsetHeight) * 100; 50 | 51 | preview.style.backgroundPosition = `${x}% ${y}%`; 52 | } -------------------------------------------------------------------------------- /docs/js/demos/custom-move.js: -------------------------------------------------------------------------------- 1 | import displace from 'dist/displace.min.js'; 2 | 3 | const DIV_CLASS = '.custom-fn__div'; 4 | const WRAPPER_CLASS = '.custom-fn__wrapper'; 5 | const LINE_CLASS = 'custom-fn__line'; 6 | 7 | const LINE_SPACING = 50; 8 | 9 | const wrapper = document.querySelector(WRAPPER_CLASS); 10 | 11 | // add lines 12 | addLines(wrapper); 13 | 14 | // start everything 15 | const el = document.querySelector(DIV_CLASS); 16 | const options = { 17 | constrain: true, 18 | relativeTo: wrapper, 19 | customMove(el, x, y){ 20 | const left = Math.round(x / LINE_SPACING) * LINE_SPACING; 21 | const top = Math.round(y / LINE_SPACING) * LINE_SPACING; 22 | el.style.left = left + 'px'; 23 | el.style.top = top + 'px'; 24 | } 25 | }; 26 | displace(el, options); 27 | 28 | 29 | function addLines(wrapper){ 30 | const w = wrapper.offsetWidth; 31 | const h = wrapper.offsetHeight; 32 | const lines = document.createDocumentFragment(); 33 | 34 | // vertical lines 35 | for (let i=1; i void 8 | // Removes event listeners and destroys instance. 9 | destroy: () => void 10 | }; 11 | 12 | type DisplaceJSOptions = { 13 | // Constrains element to its parent container. 14 | constrain?: boolean, 15 | 16 | // Constrains element to the specified DOM element. Requires constrain to be true. 17 | relativeTo?: HTMLElement, 18 | 19 | // Assigns a child element as the moveable handle for the parent element. 20 | handle?: HTMLElement, 21 | 22 | // Allows you to highlight text in inputs and textareas by disabling drag 23 | // events originating from those elements. 24 | highlightInputs?: boolean, 25 | 26 | // Function that allows you to prevent dragging from an event. 27 | // If the function returns true, the event will be ignored. 28 | ignoreFn?: DisplaceJSIgnoreFunction, 29 | 30 | // Interaction events. 31 | onMouseDown?: DisplaceJSMouseEvent, 32 | onMouseMove?: DisplaceJSMouseEvent, 33 | onMouseUp?: DisplaceJSMouseEvent, 34 | onTouchStart?: DisplaceJSTouchEvent, 35 | onTouchMove?: DisplaceJSTouchEvent, 36 | onTouchStop?: DisplaceJSTouchEvent, 37 | 38 | // Function that can be used to override how x and y are being set on the 39 | // displaced element on move. 40 | customMove?: DisplaceJSMove 41 | }; 42 | 43 | type DisplaceJSMouseEvent = (element: HTMLElement, event: MouseEvent) => void; 44 | 45 | type DisplaceJSTouchEvent = (element: HTMLElement, event: TouchEvent) => void; 46 | 47 | type DisplaceJSIgnoreFunction = (event: Event) => boolean; 48 | 49 | type DisplaceJSMove = (element: HTMLElement, xMovement: number, yMovement: number) => void; 50 | -------------------------------------------------------------------------------- /docs/scss/main/_basic-demo.scss: -------------------------------------------------------------------------------- 1 | 2 | .demo { 3 | padding: 4em; 4 | &__wrapper { 5 | width: 600px; 6 | } 7 | &__example { 8 | width: 100%; 9 | } 10 | &__actions { 11 | border: 2px solid $b3; 12 | border-radius: 4px 4px 0 0; 13 | button { 14 | padding: 14px 0; 15 | width: 16.6%; 16 | cursor: pointer; 17 | font-family: $font-main; 18 | color: lighten($b5, 20%); 19 | &:not(:first-child){ 20 | border-left: 2px solid $b3; 21 | } 22 | &.selected { 23 | color: $b3; 24 | font-weight: bold; 25 | } 26 | } 27 | } 28 | 29 | 30 | &__code { 31 | margin-top: 0 !important; 32 | border-radius: 0 0 4px 4px; 33 | } 34 | 35 | &__tag { 36 | position: absolute; 37 | left: 7px; 38 | top: 4px; 39 | font-size: 1.3rem; 40 | color: $b2; 41 | } 42 | &__boxes { 43 | position: relative; 44 | height: 400px; 45 | background: $c2; 46 | } 47 | 48 | &__one { 49 | display: inline-block; 50 | background: #ffb300; 51 | height: 330px; 52 | position: relative; 53 | left: 70px; 54 | top: 40px; 55 | width: 500px; 56 | } 57 | 58 | &__two { 59 | display: inline-block; 60 | // background: $c1; 61 | background: #ffd700; 62 | height: 70%; 63 | position: relative; 64 | left: 50px; 65 | top: 50px; 66 | width: 390px; 67 | } 68 | } 69 | 70 | .moveable { 71 | position: absolute; 72 | width: 100px; 73 | height: 100px; 74 | background: #007bff; 75 | 76 | -webkit-user-select: none; 77 | -moz-user-select: none; 78 | -ms-user-select: none; 79 | user-select: none; 80 | 81 | cursor: move; 82 | z-index: 5; 83 | transition: transform 0.15s ease-out, background 0.15s ease-out; 84 | &.active { 85 | // transform: rotate(15deg); 86 | transform: scale(1.2); 87 | background: #00bacf; 88 | } 89 | &__msg { 90 | font-size: 1.2rem; 91 | display: block; 92 | color: white; 93 | text-align: center; 94 | line-height: 100px; 95 | } 96 | } 97 | 98 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "displacejs", 3 | "version": "1.4.0", 4 | "description": "Tiny javascript library to create moveable DOM elements.", 5 | "main": "dist/displace.min.js", 6 | "scripts": { 7 | "lib-build": "cross-env NODE_PATH=. webpack --config ./webpack.config.build.js && cross-env NODE_PATH=. NODE_ENV=prod webpack --config ./webpack.config.build.js", 8 | "lib-dev": "cross-env NODE_PATH=. webpack -w --config ./webpack.config.build.js", 9 | "server": "http-server ./docs -p 3001 -c-1", 10 | "docs-js-watch": "cross-env NODE_PATH=. webpack -w --config ./webpack.config.docs.js", 11 | "docs-css-watch": "node-sass -w --output-style compressed --include-path ./node_modules docs/scss/style.scss docs/style.css", 12 | "docs-dev": "npm run docs-css-watch & npm run docs-js-watch & npm run server" 13 | }, 14 | "engines": { 15 | "node": ">=5.6.0" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/catc/displace.git" 20 | }, 21 | "keywords": [ 22 | "moveable", 23 | "drag" 24 | ], 25 | "author": "Catalin Covic", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/catc/displace/issues" 29 | }, 30 | "babel": { 31 | "presets": [ 32 | "es2015" 33 | ], 34 | "plugins": [ 35 | [ 36 | "transform-es2015-classes", 37 | { 38 | "loose": true 39 | } 40 | ] 41 | ] 42 | }, 43 | "homepage": "https://github.com/catc/displace", 44 | "dependencies": {}, 45 | "devDependencies": { 46 | "babel-core": "^6.14.0", 47 | "babel-eslint": "^6.0.5", 48 | "babel-loader": "^6.2.5", 49 | "babel-preset-es2015": "^6.14.0", 50 | "cross-env": "^6.0.3", 51 | "eslint": "^2.13.1", 52 | "highlight.js": "^9.6.0", 53 | "http-server": "^0.9.0", 54 | "node-sass": "^4.13.0", 55 | "webpack": "^1.13.2" 56 | }, 57 | "types": "./src/displace.d.ts" 58 | } 59 | -------------------------------------------------------------------------------- /docs/scss/main/_sorting-demo.scss: -------------------------------------------------------------------------------- 1 | 2 | .sorting-demo { 3 | width: 600px; 4 | background: rgba(255, 193, 7, 0.17); 5 | * { 6 | -webkit-user-select: none; 7 | -moz-user-select: none; 8 | -ms-user-select: none; 9 | user-select: none; 10 | box-sizing: border-box; 11 | } 12 | 13 | &__wrapper { 14 | height: 400px; 15 | position: relative; 16 | border-radius: 2px; 17 | } 18 | &__corner { 19 | width: 100px; 20 | height: 100px; 21 | display: inline-block; 22 | position: absolute; 23 | background: $c1; 24 | border: 2px solid $c2; 25 | border-radius: 2px; 26 | 27 | &.top-left { 28 | top: 0; 29 | left: 0; 30 | } 31 | &.top-right { 32 | top: 0; 33 | right: 0; 34 | } 35 | &.bottom-left { 36 | bottom: 0; 37 | left: 0; 38 | } 39 | &.bottom-right { 40 | bottom: 0; 41 | right: 0; 42 | } 43 | } 44 | &__sortable { 45 | $size: 40px; 46 | width: $size; 47 | height: $size; 48 | display: inline-block; 49 | border-radius: 99px; 50 | position: absolute; 51 | top: 0; 52 | left: 0; 53 | cursor: move; 54 | transition: transform 0.1s ease-out, 55 | opacity 0.3s ease-out, 56 | background 0.3s ease-out; 57 | border: 4px solid; 58 | 59 | &.active { 60 | transform: scale(1.1); 61 | z-index: 5; 62 | border-style: dotted; 63 | } 64 | &.inactive { 65 | cursor: not-allowed; 66 | background: $b6 !important; 67 | border-color: $b5 !important; 68 | } 69 | 70 | &.one { 71 | background: #2196f3; 72 | border-color: #1380d8; 73 | } 74 | &.two { 75 | background: #00BCD4; 76 | border-color: #03a3b7; 77 | } 78 | &.three { 79 | background: #4CAF50; 80 | border-color: #3a983e; 81 | } 82 | &.four { 83 | background: #8BC34A; 84 | border-color: #78ad3b; 85 | } 86 | } 87 | 88 | &__progress { 89 | font-size: 1.3rem; 90 | width: 100%; 91 | display: inline-block; 92 | text-align: center; 93 | margin-top: 6px; 94 | } 95 | 96 | &__options { 97 | margin-top: 1em; 98 | width: 600px; 99 | } 100 | &__reset-button { 101 | padding: 10px 14px; 102 | border-radius: 4px; 103 | float: right; 104 | transition: 0.2s ease-out; 105 | font-weight: bold; 106 | color: $b5; 107 | border: 2px solid $b5; 108 | &:hover { 109 | color: $b2; 110 | border-color: $b2; 111 | } 112 | } 113 | &__code-link { 114 | color: #0b97ed; 115 | display: inline-block; 116 | line-height: 40px; 117 | } 118 | } -------------------------------------------------------------------------------- /docs/js/demos/sorting.js: -------------------------------------------------------------------------------- 1 | import displace from 'dist/displace.min.js'; 2 | 3 | // sorting example 4 | let displaceInstances; 5 | function initSortingDemo() { 6 | // clear any existing displace instances 7 | try { 8 | displaceInstances.forEach(d => d.destroy()); 9 | } catch (e) { } 10 | 11 | const circleSize = 40; 12 | const positions = {}; 13 | const progress = {}; 14 | 15 | // setup corner coordinates 16 | ['.top-left', '.top-right', '.bottom-left', '.bottom-right'].map(corner => { 17 | const el = document.querySelector(corner); 18 | const position = { 19 | top: el.offsetTop, 20 | left: el.offsetLeft, 21 | // ensure that circle is completely in the box 22 | bottom: el.offsetTop + el.offsetHeight - circleSize, 23 | right: el.offsetLeft + el.offsetWidth - circleSize 24 | }; 25 | const key = corner.replace(/\./, '').replace(/-/, ' '); 26 | progress[key] = 0; 27 | return positions[key] = position; 28 | }); 29 | 30 | // update corner progress text 31 | updateCorners(); 32 | 33 | // set up circles 34 | displaceInstances = ['.one', '.two', '.three', '.four'].map(selector => { 35 | const el = document.querySelector(selector); 36 | el.style.left = genPos(105, 445) + 'px'; 37 | el.style.top = genPos(105, 245) + 'px'; 38 | el.className = el.className.replace('inactive', ''); 39 | return displace(el, { 40 | onMouseDown: activeClass, 41 | onTouchStart: activeClass, 42 | onMouseUp: inactiveClass, 43 | onTouchStop: inactiveClass, 44 | }); 45 | 46 | function genPos(min, max) { 47 | return Math.floor(Math.random() * (max - min + 1) + min); 48 | } 49 | }); 50 | 51 | function activeClass(el) { 52 | el.className += ' active'; 53 | } 54 | function inactiveClass(el) { 55 | el.className = el.className.replace('active', ''); 56 | checkPosition(el); 57 | } 58 | 59 | function checkPosition(el) { 60 | const x = el.offsetLeft; 61 | const y = el.offsetTop; 62 | 63 | Object.keys(positions).forEach(key => { 64 | const vals = positions[key]; 65 | 66 | if (between(x, vals.left, vals.right) && between(y, vals.top, vals.bottom)) { 67 | progress[key] = progress[key] + 1; 68 | updateCorners(); 69 | 70 | // disable element 71 | el.className += ' inactive'; 72 | const d = displaceInstances.find(d => { 73 | return d.el === el; 74 | }); 75 | d.destroy(); 76 | } 77 | }); 78 | 79 | function between(val, min, max) { 80 | return val >= min && val <= max; 81 | } 82 | } 83 | 84 | function updateCorners() { 85 | Object.keys(positions).forEach(key => { 86 | const query = `[data-name='${key}']`; 87 | const el = document.querySelector(query); 88 | el.innerHTML = `Contains: ${progress[key]}`; 89 | }); 90 | } 91 | } 92 | initSortingDemo(); 93 | 94 | document.querySelector('.sorting-demo__reset-button').addEventListener('click', initSortingDemo, false); 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # displace.js 8 | 9 | A minimal javascript library to enable moveable DOM elements. 10 | - ~1.2kb gzipped 11 | - supports mobile touch 12 | - no dependencies/bloat 13 | - IE9+ 14 | 15 | ## Getting started 16 | #### Reference 17 | Install via npm: 18 | 19 | ```sh 20 | npm install --save displacejs 21 | ``` 22 | 23 | Reference `displace.min.js` and use via `displace`: 24 | ```javascript 25 | // es6 26 | import displace from 'displacejs'; 27 | 28 | // commonjs 29 | let displace = require('displace'); 30 | 31 | // if using globally in browser 32 | const displace = window.displacejs; 33 | ``` 34 | 35 | #### Initialize 36 | Initialize and use: 37 | ```javascript 38 | // initial 39 | const d = displace(document.querySelector('.some-div'), options); 40 | ``` 41 | 42 | ## API 43 | 44 | ### Methods 45 | #### `displace(element, options)` 46 | Creates a new displace instance with a DOM element. For options, see [below](#options). 47 | 48 | #### `reinit()` 49 | Runs setup again. Useful when divs have been moved or resized. 50 | 51 | #### `displace.destroy()` 52 | Removes event listeners and destroys instance. 53 | 54 |
55 | 56 | ### Options 57 | #### `constrain` 58 | Constrains element to its parent container 59 | ##### Default: `false` 60 | 61 | #### `relativeTo` 62 | Constrains element to the specified DOM element. Requires `constrain` to be `true`. 63 | ##### Default: `null` 64 | 65 | #### `handle` 66 | Assigns a child element as the moveable handle for the parent element. 67 | ##### Default: `null` 68 | 69 | #### `highlightInputs` 70 | Allows you to highlight text in inputs and textareas by disabling drag events originating from those elements. 71 | ##### Default: `false` 72 | 73 | #### `ignoreFn` 74 | Function that allows you to prevent dragging from an event. If the function returns true, the event will be ignored. 75 | ##### Default: `null` 76 | 77 |
78 | 79 | #### `onMouseDown` 80 | Function that is triggered when user clicks down on moveable element. 81 | ##### Default: `null` 82 | 83 | #### `onMouseMove` 84 | Function that is triggered when user moves element. 85 | ##### Default: `null` 86 | 87 | #### `onMouseUp` 88 | Function that is triggered when user clicks up on moveable element. 89 | ##### Default: `null` 90 | 91 | #### `onTouchStart` 92 | Function that is triggered when initiates touch event. 93 | ##### Default: `null` 94 | 95 | #### `onTouchMove` 96 | Function that is triggered when moves element during touch event. 97 | ##### Default: `null` 98 | 99 | #### `onTouchStop` 100 | Function that is triggered when user ends touch event. 101 | ##### Default: `null` 102 | 103 |
104 | 105 | #### `customMove` 106 | Function that can be used to override how x and y are being set on the displaced element on move. 107 | ##### Default: `null` 108 | 109 | ## Development 110 | Clone the repo and `npm install`. Available `npm` scripts are: 111 | - build library: `lib-build` 112 | - library development (watch src files and build on change): `lib-dev` 113 | - docs development (port 3001): `docs-dev` 114 | -------------------------------------------------------------------------------- /docs/js/demos/intro.js: -------------------------------------------------------------------------------- 1 | import highlight from 'highlight.js'; 2 | import displace from 'dist/displace.min.js'; 3 | 4 | window.displace = displace; 5 | 6 | const code = { 7 | 0: 8 | `// regular instantiation 9 | const el = document.querySelector('.moveable'); 10 | displace(el); 11 | `, 12 | 1: 13 | `// constrained to parent container 14 | const el = document.querySelector('.moveable'); 15 | const options = { 16 | constrain: true 17 | }; 18 | displace(el, options); 19 | `, 20 | 2: 21 | `// constrained relative to specified container 22 | const el = document.querySelector('.moveable'); 23 | const options = { 24 | constrain: true, 25 | relativeTo: document.querySelector('.box-1') 26 | }; 27 | displace(el, options); 28 | `, 29 | 3: 30 | `// trigger events 31 | const el = document.querySelector('.moveable'); 32 | const options = { 33 | constrain: true, 34 | relativeTo: document.body, 35 | 36 | onMouseDown: active, 37 | onTouchStart: active, 38 | onMouseUp: inactive, 39 | onTouchStop: inactive 40 | }; 41 | 42 | function active(el){ 43 | el.className += ' active'; 44 | } 45 | function inactive(el){ 46 | el.className = el.className.replace('active', ''); 47 | } 48 | 49 | displace(el, options); 50 | `, 51 | 4: 52 | `// make 'Box 3' moveable with the 'Drag me' box as the handle 53 | const el = document.querySelector('.box-3'); 54 | const options = { 55 | constrain: true, 56 | handle: document.querySelector('.moveable'), 57 | relativeTo: document.querySelector('.box-1') 58 | }; 59 | displace(el, options); 60 | `, 61 | 5: 62 | `// make 'Box 2' moveable with 'Box 3' as its handle, 63 | // but ignore clicks to the 'Drag me' box 64 | const el = document.querySelector('.box-2'); 65 | const options = { 66 | constrain: true, 67 | handle: document.querySelector('.box-3'), 68 | relativeTo: document.querySelector('.box-1'), 69 | ignoreFn: function(event) { 70 | return event.target 71 | .closest('.moveable') != null 72 | } 73 | }; 74 | displace(el, options);` 75 | }; 76 | 77 | const codeEl = document.querySelector('.demo__code code'); 78 | const moveableEl = document.querySelector('.moveable'); 79 | const box3 = document.querySelector('.box-3'); 80 | 81 | const buttons = document.querySelectorAll('.demo__actions button'); 82 | [].forEach.call(buttons, (button, i) => { 83 | button.addEventListener('click', selectCode.bind(null, i)); 84 | }); 85 | 86 | let displaceInstance; 87 | function selectCode(i) { 88 | [].forEach.call(buttons, button => { 89 | button.className = ''; 90 | }); 91 | buttons[i].className = 'selected'; 92 | let c = code[i]; 93 | 94 | // update code 95 | codeEl.innerHTML = c; 96 | highlight.highlightBlock(codeEl); 97 | 98 | // reset element 99 | moveableEl.style.top = '50px'; 100 | moveableEl.style.left = '50px'; 101 | 102 | // reset box 3 (due to handle example) 103 | box3.style.top = '50px'; 104 | box3.style.left = '50px'; 105 | box3.style.position = 'relative'; 106 | 107 | // destroy old instance of displace if exists 108 | if (displaceInstance && displaceInstance.destroy) { 109 | displaceInstance.destroy(); 110 | } 111 | 112 | // prepare code to actually run; 113 | c = c.replace(/const\b/gi, 'var'); 114 | /* 115 | add return statement to store displace instance 116 | in order to destroy it before re-init for other demos 117 | */ 118 | c = c.replace(/displace\(/, 'return displace('); 119 | const fn = new Function(c); 120 | 121 | displaceInstance = fn(); 122 | } 123 | 124 | // start first basic demo example 125 | selectCode(0); -------------------------------------------------------------------------------- /src/displace.js: -------------------------------------------------------------------------------- 1 | import { 2 | generateClamp, 3 | isRelative, 4 | generateMoveFn 5 | } from 'utils'; 6 | 7 | import { 8 | // mouse 9 | mousedown, 10 | mouseup, 11 | 12 | // touch 13 | touchstart, 14 | touchstop 15 | } from 'events'; 16 | 17 | const moveFn = generateMoveFn(); 18 | 19 | const defaultOpts = { 20 | constrain: false, 21 | relativeTo: null, 22 | handle: null, 23 | ignoreFn: null, 24 | highlightInputs: false, 25 | 26 | // events 27 | onMouseDown: null, 28 | onMouseMove: null, 29 | onMouseUp: null, 30 | onTouchStart: null, 31 | onTouchMove: null, 32 | onTouchStop: null, 33 | 34 | customMove: null 35 | }; 36 | 37 | 38 | class Displace { 39 | constructor(el, opts){ 40 | if (!el){ 41 | throw Error('Must include moveable element'); 42 | } 43 | this.el = el; 44 | this.opts = opts; 45 | 46 | // init 47 | setup.call(this); 48 | } 49 | 50 | reinit(){ 51 | this.destroy(); 52 | setup.call(this); 53 | } 54 | destroy(){ 55 | const events = this.events; 56 | 57 | this.handle.removeEventListener('mousedown', events.mousedown, false); 58 | document.removeEventListener('mousemove', events.mousemove, false); 59 | document.removeEventListener('mouseup', events.mouseup, false); 60 | 61 | this.handle.removeEventListener('touchstart', events.touchstart, false); 62 | document.removeEventListener('touchmove', events.touchmove, false); 63 | document.removeEventListener('touchstop', events.touchstop, false); 64 | document.removeEventListener('touchmove', this.events.scrollFix, { passive: false }); 65 | } 66 | } 67 | 68 | function setup(){ 69 | const el = this.el; 70 | const opts = this.opts || defaultOpts; 71 | const data = {}; 72 | 73 | // set required css 74 | el.style.position = 'absolute'; 75 | 76 | // set the handle 77 | this.handle = opts.handle || el; 78 | 79 | // generate min / max ranges 80 | if (opts.constrain){ 81 | const relTo = opts.relativeTo || el.parentNode; 82 | 83 | let traverse = el; 84 | let minX = 0; 85 | let minY = 0; 86 | while (traverse !== relTo){ 87 | traverse = traverse.parentNode; 88 | if (isRelative(traverse)){ 89 | minX -= traverse.offsetLeft; 90 | minY -= traverse.offsetTop; 91 | } 92 | if (traverse === relTo){ 93 | minX += traverse.offsetLeft; 94 | minY += traverse.offsetTop; 95 | } 96 | } 97 | 98 | const maxX = minX + relTo.offsetWidth - el.offsetWidth; 99 | const maxY = minY + relTo.offsetHeight - el.offsetHeight; 100 | 101 | data.xClamp = generateClamp(minX, maxX); 102 | data.yClamp = generateClamp(minY, maxY); 103 | } 104 | 105 | this.opts = opts; 106 | this.data = data; 107 | this.events = { 108 | // mouse events 109 | mousedown: mousedown.bind(this), 110 | mouseup: mouseup.bind(this), 111 | 112 | // touch events 113 | touchstart: touchstart.bind(this), 114 | touchstop: touchstop.bind(this), 115 | 116 | // disable scrolling on mobile while dragging 117 | // https://github.com/bevacqua/dragula/issues/487 118 | scrollFix: e => { 119 | if (this.isDragging){ 120 | e.preventDefault(); 121 | } 122 | } 123 | }; 124 | 125 | // create move function - either use default move functionality or custom (if provided) 126 | this.handleMove = moveFn(this.opts.customMove); 127 | 128 | // add init events to handle 129 | this.handle.addEventListener('mousedown', this.events.mousedown, false); 130 | this.handle.addEventListener('touchstart', this.events.touchstart, false); 131 | 132 | // scroll fix for mobile 133 | document.addEventListener('touchmove', this.events.scrollFix, { passive: false }); 134 | } 135 | 136 | // export factory fn 137 | export default (el, opts) => new Displace(el, opts); 138 | -------------------------------------------------------------------------------- /src/events.js: -------------------------------------------------------------------------------- 1 | // mouse events 2 | export function mousedown(e){ 3 | const opts = this.opts; 4 | if (opts.highlightInputs){ 5 | // allow for selection of text in inputs/textareas 6 | const target = e.target.tagName.toLowerCase(); 7 | if (target === 'input' || target === 'textarea'){ 8 | return; 9 | } 10 | } 11 | 12 | if (opts.ignoreFn && opts.ignoreFn(e)) { 13 | return; 14 | } 15 | 16 | // only left button is clicked 17 | if (e.button === 0){ 18 | const el = this.el; 19 | const events = this.events; 20 | 21 | if (typeof opts.onMouseDown === 'function'){ 22 | opts.onMouseDown(el, e); 23 | } 24 | 25 | // determine initial offset and bind to mousemove handler 26 | let wOff = e.clientX - el.offsetLeft; 27 | let hOff = e.clientY - el.offsetTop; 28 | events.mousemove = mousemove.bind(this, wOff, hOff); 29 | 30 | document.addEventListener('mousemove', events.mousemove, false); 31 | document.addEventListener('mouseup', events.mouseup, false); 32 | } 33 | 34 | // prevent highlighting text when dragging (IE) 35 | e.preventDefault(); 36 | }; 37 | 38 | export function mousemove(offsetW, offsetH, e){ 39 | const el = this.el; 40 | const opts = this.opts; 41 | const data = this.data; 42 | 43 | if (typeof opts.onMouseMove === 'function'){ 44 | opts.onMouseMove(el, e); 45 | } 46 | 47 | let x = e.clientX - offsetW; 48 | let y = e.clientY - offsetH; 49 | 50 | if (opts.constrain){ 51 | // clamp values if out of range 52 | x = data.xClamp(x); 53 | y = data.yClamp(y); 54 | } 55 | this.handleMove(el, x, y); 56 | 57 | // prevent highlighting text when dragging 58 | e.preventDefault(); 59 | return false; 60 | }; 61 | 62 | export function mouseup(e){ 63 | const el = this.el; 64 | const opts = this.opts; 65 | const events = this.events; 66 | 67 | if (typeof opts.onMouseUp === 'function'){ 68 | opts.onMouseUp(el, e); 69 | } 70 | 71 | document.removeEventListener('mouseup', events.mouseup, false); 72 | document.removeEventListener('mousemove', events.mousemove, false); 73 | }; 74 | 75 | // touch events 76 | export function touchstart(e){ 77 | const opts = this.opts; 78 | if (opts.highlightInputs){ 79 | // allow for selection of text in inputs/textareas 80 | const target = e.target.tagName.toLowerCase(); 81 | if (target === 'input' || target === 'textarea'){ 82 | return; 83 | } 84 | } 85 | 86 | if (opts.ignoreFn && opts.ignoreFn(e)) { 87 | return; 88 | } 89 | 90 | const el = this.el; 91 | const events = this.events; 92 | 93 | if (typeof opts.onTouchStart === 'function'){ 94 | opts.onTouchStart(el, e); 95 | } 96 | 97 | const touch = e.targetTouches[0]; 98 | let wOff = touch.clientX - el.offsetLeft; 99 | let hOff = touch.clientY - el.offsetTop; 100 | 101 | events.touchmove = touchmove.bind(this, wOff, hOff); 102 | 103 | // disable scrolling 104 | this.isDragging = true; 105 | 106 | document.addEventListener('touchmove', events.touchmove, false); 107 | document.addEventListener('touchend', events.touchstop, false); 108 | document.addEventListener('touchcancel', events.touchstop, false); 109 | }; 110 | 111 | export function touchmove(offsetW, offsetH, e){ 112 | const el = this.el; 113 | const opts = this.opts; 114 | const data = this.data; 115 | 116 | if (typeof opts.onTouchMove === 'function'){ 117 | opts.onTouchMove(el, e); 118 | } 119 | 120 | const touch = e.targetTouches[0]; 121 | let x = touch.clientX - offsetW; 122 | let y = touch.clientY - offsetH; 123 | 124 | if (opts.constrain){ 125 | // clamp values if out of range 126 | x = data.xClamp(x); 127 | y = data.yClamp(y); 128 | } 129 | this.handleMove(el, x, y); 130 | 131 | // prevent highlighting text when dragging 132 | e.preventDefault(); 133 | return false; 134 | }; 135 | 136 | export function touchstop(e){ 137 | // re-enable scrolling 138 | this.isDragging = false; 139 | 140 | const el = this.el; 141 | const opts = this.opts; 142 | const events = this.events; 143 | 144 | if (typeof opts.onTouchStop === 'function'){ 145 | opts.onTouchStop(el, e); 146 | } 147 | 148 | document.removeEventListener('touchmove', events.touchmove, false); 149 | document.removeEventListener('touchend', events.touchstop, false); 150 | document.removeEventListener('touchcancel', events.touchstop, false); 151 | }; -------------------------------------------------------------------------------- /docs/fonts/ico.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by IcoMoon 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /dist/displace.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * displacejs.js 1.3.2 - Tiny javascript library to create moveable DOM elements. 3 | * Copyright (c) 2019 Catalin Covic - https://github.com/catc/displace 4 | * License: MIT 5 | */ 6 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.displacejs=t():e.displacejs=t()}(this,function(){return function(e){function t(n){if(o[n])return o[n].exports;var s=o[n]={exports:{},id:n,loaded:!1};return e[n].call(s.exports,s,s.exports,t),s.loaded=!0,s.exports}var o={};return t.m=e,t.c=o,t.p="",t(0)}([function(e,t,o){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}var s=o(1),i=n(s);e.exports=i.default},function(e,t,o){"use strict";function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function s(){var e=this,t=this.el,o=this.opts||c,n={};if(t.style.position="absolute",this.handle=o.handle||t,o.constrain){for(var s=o.relativeTo||t.parentNode,a=t,h=0,v=0;a!==s;)a=a.parentNode,(0,i.isRelative)(a)&&(h-=a.offsetLeft,v-=a.offsetTop),a===s&&(h+=a.offsetLeft,v+=a.offsetTop);var l=h+s.offsetWidth-t.offsetWidth,f=v+s.offsetHeight-t.offsetHeight;n.xClamp=(0,i.generateClamp)(h,l),n.yClamp=(0,i.generateClamp)(v,f)}this.opts=o,this.data=n,this.events={mousedown:u.mousedown.bind(this),mouseup:u.mouseup.bind(this),touchstart:u.touchstart.bind(this),touchstop:u.touchstop.bind(this),scrollFix:function(t){e.isDragging&&t.preventDefault()}},this.handleMove=r(this.opts.customMove),this.handle.addEventListener("mousedown",this.events.mousedown,!1),this.handle.addEventListener("touchstart",this.events.touchstart,!1),document.addEventListener("touchmove",this.events.scrollFix,{passive:!1})}Object.defineProperty(t,"__esModule",{value:!0});var i=o(2),u=o(3),r=(0,i.generateMoveFn)(),c={constrain:!1,relativeTo:null,handle:null,ignoreFn:null,highlightInputs:!1,onMouseDown:null,onMouseMove:null,onMouseUp:null,onTouchStart:null,onTouchMove:null,onTouchStop:null,customMove:null},a=function(){function e(t,o){if(n(this,e),!t)throw Error("Must include moveable element");this.el=t,this.opts=o,s.call(this)}return e.prototype.reinit=function(){this.destroy(),s.call(this)},e.prototype.destroy=function(){var e=this.events;this.handle.removeEventListener("mousedown",e.mousedown,!1),document.removeEventListener("mousemove",e.mousemove,!1),document.removeEventListener("mouseup",e.mouseup,!1),this.handle.removeEventListener("touchstart",e.touchstart,!1),document.removeEventListener("touchmove",e.touchmove,!1),document.removeEventListener("touchstop",e.touchstop,!1),document.removeEventListener("touchmove",this.events.scrollFix,{passive:!1})},e}();t.default=function(e,t){return new a(e,t)}},function(e,t){"use strict";function o(e,t){return function(o){return Math.min(Math.max(o,e),t)}}function n(e){return"relative"===window.getComputedStyle(e).position}function s(){return window.requestAnimationFrame?function(e){var t=e||i;return function(e,o,n){window.requestAnimationFrame(function(){t(e,o,n)})}}:function(e){return function(t,o,n){var s=e||i;s(t,o,n)}}}function i(e,t,o){e.style.left=t+"px",e.style.top=o+"px"}Object.defineProperty(t,"__esModule",{value:!0}),t.generateClamp=o,t.isRelative=n,t.generateMoveFn=s},function(e,t){"use strict";function o(e){var t=this.opts;if(t.highlightInputs){var o=e.target.tagName.toLowerCase();if("input"===o||"textarea"===o)return}if(!t.ignoreFn||!t.ignoreFn(e)){if(0===e.button){var s=this.el,i=this.events;"function"==typeof t.onMouseDown&&t.onMouseDown(s,e);var u=e.clientX-s.offsetLeft,r=e.clientY-s.offsetTop;i.mousemove=n.bind(this,u,r),document.addEventListener("mousemove",i.mousemove,!1),document.addEventListener("mouseup",i.mouseup,!1)}e.preventDefault()}}function n(e,t,o){var n=this.el,s=this.opts,i=this.data;"function"==typeof s.onMouseMove&&s.onMouseMove(n,o);var u=o.clientX-e,r=o.clientY-t;return s.constrain&&(u=i.xClamp(u),r=i.yClamp(r)),this.handleMove(n,u,r),o.preventDefault(),!1}function s(e){var t=this.el,o=this.opts,n=this.events;"function"==typeof o.onMouseUp&&o.onMouseUp(t,e),document.removeEventListener("mouseup",n.mouseup,!1),document.removeEventListener("mousemove",n.mousemove,!1)}function i(e){var t=this.opts;if(t.highlightInputs){var o=e.target.tagName.toLowerCase();if("input"===o||"textarea"===o)return}if(!t.ignoreFn||!t.ignoreFn(e)){var n=this.el,s=this.events;"function"==typeof t.onTouchStart&&t.onTouchStart(n,e);var i=e.targetTouches[0],r=i.clientX-n.offsetLeft,c=i.clientY-n.offsetTop;s.touchmove=u.bind(this,r,c),this.isDragging=!0,document.addEventListener("touchmove",s.touchmove,!1),document.addEventListener("touchend",s.touchstop,!1),document.addEventListener("touchcancel",s.touchstop,!1)}}function u(e,t,o){var n=this.el,s=this.opts,i=this.data;"function"==typeof s.onTouchMove&&s.onTouchMove(n,o);var u=o.targetTouches[0],r=u.clientX-e,c=u.clientY-t;return s.constrain&&(r=i.xClamp(r),c=i.yClamp(c)),this.handleMove(n,r,c),o.preventDefault(),!1}function r(e){this.isDragging=!1;var t=this.el,o=this.opts,n=this.events;"function"==typeof o.onTouchStop&&o.onTouchStop(t,e),document.removeEventListener("touchmove",n.touchmove,!1),document.removeEventListener("touchend",n.touchstop,!1),document.removeEventListener("touchcancel",n.touchstop,!1)}Object.defineProperty(t,"__esModule",{value:!0}),t.mousedown=o,t.mousemove=n,t.mouseup=s,t.touchstart=i,t.touchmove=u,t.touchstop=r}])}); -------------------------------------------------------------------------------- /docs/style.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v4.2.0 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:0.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace, monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace, monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,html [type="button"],[type="reset"],[type="submit"]{-webkit-appearance:button}button::-moz-focus-inner,[type="button"]::-moz-focus-inner,[type="reset"]::-moz-focus-inner,[type="submit"]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type="button"]:-moz-focusring,[type="reset"]:-moz-focusring,[type="submit"]:-moz-focusring{outline:1px dotted ButtonText}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type="checkbox"],[type="radio"]{box-sizing:border-box;padding:0}[type="number"]::-webkit-inner-spin-button,[type="number"]::-webkit-outer-spin-button{height:auto}[type="search"]{-webkit-appearance:textfield;outline-offset:-2px}[type="search"]::-webkit-search-cancel-button,[type="search"]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none}html{font-size:62.5%;overflow-y:scroll;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{background-color:#f7f7f7;color:#404040;font-family:"Montserrat",sans-serif;font-size:1.4em;font-style:normal;font-weight:400;line-height:1.55;position:relative;-webkit-font-smoothing:antialiased;font-smoothing:antialiased}h1,h2,h3,h4,h5,h6{text-rendering:optimizelegibility;-webkit-font-smoothing:antialiased}a{color:inherit;text-decoration:underline}code{font-family:"Oxygen Mono",monospace}button{border:none;background:none;box-shadow:none;text-shadow:none;-webkit-appearance:none;-moz-appearance:none;font-family:"Montserrat",sans-serif}button:not(:disabled){cursor:pointer}button:hover,button:focus,button:active{outline:none}@font-face{font-family:'ico';src:url("./fonts/ico.ttf?ib1ojd") format("truetype"),url("./fonts/ico.woff?ib1ojd") format("woff"),url("./fonts/ico.svg?ib1ojd#ico") format("svg");font-weight:normal;font-style:normal}[class^="i-"],[class*=" i-"],.intro__info li:before{font-family:'ico' !important;speak:none;font-style:normal;font-weight:normal;font-variant:normal;text-transform:none;line-height:1;font-size:2rem;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.i-check:before{content:"\e904"}.i-download:before{content:"\e900"}.i-info-with-circle:before{content:"\e62c"}.i-info2:before{content:"\e62d"}.i-power-plug:before{content:"\e901"}.i-github:before{content:"\e902"}.hljs{display:block;overflow-x:auto;padding:0.5em;background:#23241f}.hljs,.hljs-tag,.hljs-subst{color:#f8f8f2}.hljs-strong,.hljs-emphasis{color:#a8a8a2}.hljs-bullet,.hljs-quote,.hljs-number,.hljs-regexp,.hljs-literal,.hljs-link{color:#ae81ff}.hljs-code,.hljs-title,.hljs-section,.hljs-selector-class{color:#a6e22e}.hljs-strong{font-weight:bold}.hljs-emphasis{font-style:italic}.hljs-keyword,.hljs-selector-tag,.hljs-name,.hljs-attr{color:#f92672}.hljs-symbol,.hljs-attribute{color:#66d9ef}.hljs-params,.hljs-class .hljs-title{color:#f8f8f2}.hljs-string,.hljs-type,.hljs-built_in,.hljs-builtin-name,.hljs-selector-id,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-addition,.hljs-variable,.hljs-template-variable{color:#e6db74}.hljs-comment,.hljs-deletion,.hljs-meta{color:#75715e}.intro{background:#ffd044;border-bottom:1px solid #FFC107;padding:7em 4em 4em;margin-bottom:7em}.intro__description{font-size:1.7rem;color:#404040}.intro__table-of-contents{margin-top:2em}.intro__table-of-contents a{display:inline-block;font-size:1.5rem;padding:14px 20px 13px;text-decoration:none;border-radius:4px;margin-right:1em;transition:0.2s ease-out;background:#3c3c3c;color:white}.intro__table-of-contents a span{display:inline-block;margin-right:8px;vertical-align:-3px}.intro__table-of-contents a span:before{color:inherit}.intro__table-of-contents a:hover{color:#ffc107}.intro__info{list-style:none;counter-reset:list;padding-left:1em}.intro__info li{margin:6px 0}.intro__info li:before{content:"\e904";font-size:1.6rem;display:inline-block;vertical-align:-2px;margin-right:10px;color:#F27E00}.demo{padding:4em}.demo__wrapper{width:600px}.demo__example{width:100%}.demo__actions{border:2px solid #23241f;border-radius:4px 4px 0 0}.demo__actions button{padding:14px 0;width:16.6%;cursor:pointer;font-family:"Montserrat",sans-serif;color:#737373}.demo__actions button:not(:first-child){border-left:2px solid #23241f}.demo__actions button.selected{color:#23241f;font-weight:bold}.demo__code{margin-top:0 !important;border-radius:0 0 4px 4px}.demo__tag{position:absolute;left:7px;top:4px;font-size:1.3rem;color:#1A1A1A}.demo__boxes{position:relative;height:400px;background:#ff9800}.demo__one{display:inline-block;background:#ffb300;height:330px;position:relative;left:70px;top:40px;width:500px}.demo__two{display:inline-block;background:#ffd700;height:70%;position:relative;left:50px;top:50px;width:390px}.moveable{position:absolute;width:100px;height:100px;background:#007bff;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:move;z-index:5;transition:transform 0.15s ease-out, background 0.15s ease-out}.moveable.active{transform:scale(1.2);background:#00bacf}.moveable__msg{font-size:1.2rem;display:block;color:white;text-align:center;line-height:100px}.magnifier-demo{width:600px}.magnifier-demo__image-wrapper{width:400px;position:relative;display:inline-block}.magnifier-demo img{height:auto;max-width:100%;vertical-align:middle;display:block}.magnifier-demo__zoomer{display:inline-block;position:absolute;left:245px;top:102px;background:rgba(255,255,255,0.4);cursor:move;border:1px solid rgba(255,255,255,0.7)}.magnifier-demo__zoom-preview{display:inline-block;width:200px;float:right;position:relative;height:100%;border-left:3px solid #f7f7f7;box-sizing:border-box}.magnifier-demo a{color:#F27E00;text-decoration:none}.magnifier-demo__code-link{color:#0b97ed !important}.magnifier-demo__photo-ref{display:inline-block;float:right}.sorting-demo{width:600px;background:rgba(255,193,7,0.17)}.sorting-demo *{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;box-sizing:border-box}.sorting-demo__wrapper{height:400px;position:relative;border-radius:2px}.sorting-demo__corner{width:100px;height:100px;display:inline-block;position:absolute;background:#ffc107;border:2px solid #ff9800;border-radius:2px}.sorting-demo__corner.top-left{top:0;left:0}.sorting-demo__corner.top-right{top:0;right:0}.sorting-demo__corner.bottom-left{bottom:0;left:0}.sorting-demo__corner.bottom-right{bottom:0;right:0}.sorting-demo__sortable{width:40px;height:40px;display:inline-block;border-radius:99px;position:absolute;top:0;left:0;cursor:move;transition:transform 0.1s ease-out, opacity 0.3s ease-out, background 0.3s ease-out;border:4px solid}.sorting-demo__sortable.active{transform:scale(1.1);z-index:5;border-style:dotted}.sorting-demo__sortable.inactive{cursor:not-allowed;background:dimgray !important;border-color:#404040 !important}.sorting-demo__sortable.one{background:#2196f3;border-color:#1380d8}.sorting-demo__sortable.two{background:#00BCD4;border-color:#03a3b7}.sorting-demo__sortable.three{background:#4CAF50;border-color:#3a983e}.sorting-demo__sortable.four{background:#8BC34A;border-color:#78ad3b}.sorting-demo__progress{font-size:1.3rem;width:100%;display:inline-block;text-align:center;margin-top:6px}.sorting-demo__options{margin-top:1em;width:600px}.sorting-demo__reset-button{padding:10px 14px;border-radius:4px;float:right;transition:0.2s ease-out;font-weight:bold;color:#404040;border:2px solid #404040}.sorting-demo__reset-button:hover{color:#1A1A1A;border-color:#1A1A1A}.sorting-demo__code-link{color:#0b97ed;display:inline-block;line-height:40px}.custom-fn__wrapper{width:599px;height:399px;border:1px solid #1A1A1A;position:relative}.custom-fn__div{display:inline-block;width:99px;height:99px;background:#ffc107;cursor:move;top:100px;left:100px}.custom-fn__line{background:rgba(105,105,105,0.8);display:inline-block;position:absolute}body{min-width:960px;padding-bottom:4em}h1{font-size:3.4rem}h2{font-size:2.2rem;margin:4em 0 1em}h3{margin-top:1.8em;font-size:1.8rem}h4{color:#F27E00;margin:0;margin-top:2em}p.content{margin:8px 0;line-height:2.6rem}p.content a:hover{transition:0.15s ease-out;color:#F27E00}p .tab{margin-left:2em}p.top-space{margin-top:1em}.non-code{max-width:500px;padding:0 4em}.api{margin-left:2em}.api-info{color:#ff9800;color:#ffc107;display:inline-block;margin-right:9px;vertical-align:-3px}code.inline{background:#ffc107;display:inline-block;padding:0px 6px;border-radius:3px;font-size:1.3rem;line-height:1.8rem;vertical-align:1px}pre{background:#23241f;padding:2em 4em;font-size:1.3rem}pre code{line-height:1.6em;background:inherit}.code-block{margin:3em 0} 2 | -------------------------------------------------------------------------------- /docs/scss/basic/_normalize.scss: -------------------------------------------------------------------------------- 1 | /*! normalize.css v4.2.0 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /** 4 | * 1. Change the default font family in all browsers (opinionated). 5 | * 2. Correct the line height in all browsers. 6 | * 3. Prevent adjustments of font size after orientation changes in IE and iOS. 7 | */ 8 | 9 | /* Document 10 | ========================================================================== */ 11 | 12 | html { 13 | font-family: sans-serif; /* 1 */ 14 | line-height: 1.15; /* 2 */ 15 | -ms-text-size-adjust: 100%; /* 3 */ 16 | -webkit-text-size-adjust: 100%; /* 3 */ 17 | } 18 | 19 | /* Sections 20 | ========================================================================== */ 21 | 22 | /** 23 | * Remove the margin in all browsers (opinionated). 24 | */ 25 | 26 | body { 27 | margin: 0; 28 | } 29 | 30 | /** 31 | * Add the correct display in IE 9-. 32 | */ 33 | 34 | article, 35 | aside, 36 | footer, 37 | header, 38 | nav, 39 | section { 40 | display: block; 41 | } 42 | 43 | /** 44 | * Correct the font size and margin on `h1` elements within `section` and 45 | * `article` contexts in Chrome, Firefox, and Safari. 46 | */ 47 | 48 | h1 { 49 | font-size: 2em; 50 | margin: 0.67em 0; 51 | } 52 | 53 | /* Grouping content 54 | ========================================================================== */ 55 | 56 | /** 57 | * Add the correct display in IE 9-. 58 | * 1. Add the correct display in IE. 59 | */ 60 | 61 | figcaption, 62 | figure, 63 | main { /* 1 */ 64 | display: block; 65 | } 66 | 67 | /** 68 | * Add the correct margin in IE 8. 69 | */ 70 | 71 | figure { 72 | margin: 1em 40px; 73 | } 74 | 75 | /** 76 | * 1. Add the correct box sizing in Firefox. 77 | * 2. Show the overflow in Edge and IE. 78 | */ 79 | 80 | hr { 81 | box-sizing: content-box; /* 1 */ 82 | height: 0; /* 1 */ 83 | overflow: visible; /* 2 */ 84 | } 85 | 86 | /** 87 | * 1. Correct the inheritance and scaling of font size in all browsers. 88 | * 2. Correct the odd `em` font sizing in all browsers. 89 | */ 90 | 91 | pre { 92 | font-family: monospace, monospace; /* 1 */ 93 | font-size: 1em; /* 2 */ 94 | } 95 | 96 | /* Text-level semantics 97 | ========================================================================== */ 98 | 99 | /** 100 | * 1. Remove the gray background on active links in IE 10. 101 | * 2. Remove gaps in links underline in iOS 8+ and Safari 8+. 102 | */ 103 | 104 | a { 105 | background-color: transparent; /* 1 */ 106 | -webkit-text-decoration-skip: objects; /* 2 */ 107 | } 108 | 109 | /** 110 | * Remove the outline on focused links when they are also active or hovered 111 | * in all browsers (opinionated). 112 | */ 113 | 114 | a:active, 115 | a:hover { 116 | outline-width: 0; 117 | } 118 | 119 | /** 120 | * 1. Remove the bottom border in Firefox 39-. 121 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 122 | */ 123 | 124 | abbr[title] { 125 | border-bottom: none; /* 1 */ 126 | text-decoration: underline; /* 2 */ 127 | text-decoration: underline dotted; /* 2 */ 128 | } 129 | 130 | /** 131 | * Prevent the duplicate application of `bolder` by the next rule in Safari 6. 132 | */ 133 | 134 | b, 135 | strong { 136 | font-weight: inherit; 137 | } 138 | 139 | /** 140 | * Add the correct font weight in Chrome, Edge, and Safari. 141 | */ 142 | 143 | b, 144 | strong { 145 | font-weight: bolder; 146 | } 147 | 148 | /** 149 | * 1. Correct the inheritance and scaling of font size in all browsers. 150 | * 2. Correct the odd `em` font sizing in all browsers. 151 | */ 152 | 153 | code, 154 | kbd, 155 | samp { 156 | font-family: monospace, monospace; /* 1 */ 157 | font-size: 1em; /* 2 */ 158 | } 159 | 160 | /** 161 | * Add the correct font style in Android 4.3-. 162 | */ 163 | 164 | dfn { 165 | font-style: italic; 166 | } 167 | 168 | /** 169 | * Add the correct background and color in IE 9-. 170 | */ 171 | 172 | mark { 173 | background-color: #ff0; 174 | color: #000; 175 | } 176 | 177 | /** 178 | * Add the correct font size in all browsers. 179 | */ 180 | 181 | small { 182 | font-size: 80%; 183 | } 184 | 185 | /** 186 | * Prevent `sub` and `sup` elements from affecting the line height in 187 | * all browsers. 188 | */ 189 | 190 | sub, 191 | sup { 192 | font-size: 75%; 193 | line-height: 0; 194 | position: relative; 195 | vertical-align: baseline; 196 | } 197 | 198 | sub { 199 | bottom: -0.25em; 200 | } 201 | 202 | sup { 203 | top: -0.5em; 204 | } 205 | 206 | /* Embedded content 207 | ========================================================================== */ 208 | 209 | /** 210 | * Add the correct display in IE 9-. 211 | */ 212 | 213 | audio, 214 | video { 215 | display: inline-block; 216 | } 217 | 218 | /** 219 | * Add the correct display in iOS 4-7. 220 | */ 221 | 222 | audio:not([controls]) { 223 | display: none; 224 | height: 0; 225 | } 226 | 227 | /** 228 | * Remove the border on images inside links in IE 10-. 229 | */ 230 | 231 | img { 232 | border-style: none; 233 | } 234 | 235 | /** 236 | * Hide the overflow in IE. 237 | */ 238 | 239 | svg:not(:root) { 240 | overflow: hidden; 241 | } 242 | 243 | /* Forms 244 | ========================================================================== */ 245 | 246 | /** 247 | * 1. Change the font styles in all browsers (opinionated). 248 | * 2. Remove the margin in Firefox and Safari. 249 | */ 250 | 251 | button, 252 | input, 253 | optgroup, 254 | select, 255 | textarea { 256 | font-family: sans-serif; /* 1 */ 257 | font-size: 100%; /* 1 */ 258 | line-height: 1.15; /* 1 */ 259 | margin: 0; /* 2 */ 260 | } 261 | 262 | /** 263 | * Show the overflow in IE. 264 | * 1. Show the overflow in Edge. 265 | */ 266 | 267 | button, 268 | input { /* 1 */ 269 | overflow: visible; 270 | } 271 | 272 | /** 273 | * Remove the inheritance of text transform in Edge, Firefox, and IE. 274 | * 1. Remove the inheritance of text transform in Firefox. 275 | */ 276 | 277 | button, 278 | select { /* 1 */ 279 | text-transform: none; 280 | } 281 | 282 | /** 283 | * 1. Prevent a WebKit bug where (2) destroys native `audio` and `video` 284 | * controls in Android 4. 285 | * 2. Correct the inability to style clickable types in iOS and Safari. 286 | */ 287 | 288 | button, 289 | html [type="button"], /* 1 */ 290 | [type="reset"], 291 | [type="submit"] { 292 | -webkit-appearance: button; /* 2 */ 293 | } 294 | 295 | /** 296 | * Remove the inner border and padding in Firefox. 297 | */ 298 | 299 | button::-moz-focus-inner, 300 | [type="button"]::-moz-focus-inner, 301 | [type="reset"]::-moz-focus-inner, 302 | [type="submit"]::-moz-focus-inner { 303 | border-style: none; 304 | padding: 0; 305 | } 306 | 307 | /** 308 | * Restore the focus styles unset by the previous rule. 309 | */ 310 | 311 | button:-moz-focusring, 312 | [type="button"]:-moz-focusring, 313 | [type="reset"]:-moz-focusring, 314 | [type="submit"]:-moz-focusring { 315 | outline: 1px dotted ButtonText; 316 | } 317 | 318 | /** 319 | * Change the border, margin, and padding in all browsers (opinionated). 320 | */ 321 | 322 | fieldset { 323 | border: 1px solid #c0c0c0; 324 | margin: 0 2px; 325 | padding: 0.35em 0.625em 0.75em; 326 | } 327 | 328 | /** 329 | * 1. Correct the text wrapping in Edge and IE. 330 | * 2. Correct the color inheritance from `fieldset` elements in IE. 331 | * 3. Remove the padding so developers are not caught out when they zero out 332 | * `fieldset` elements in all browsers. 333 | */ 334 | 335 | legend { 336 | box-sizing: border-box; /* 1 */ 337 | color: inherit; /* 2 */ 338 | display: table; /* 1 */ 339 | max-width: 100%; /* 1 */ 340 | padding: 0; /* 3 */ 341 | white-space: normal; /* 1 */ 342 | } 343 | 344 | /** 345 | * 1. Add the correct display in IE 9-. 346 | * 2. Add the correct vertical alignment in Chrome, Firefox, and Opera. 347 | */ 348 | 349 | progress { 350 | display: inline-block; /* 1 */ 351 | vertical-align: baseline; /* 2 */ 352 | } 353 | 354 | /** 355 | * Remove the default vertical scrollbar in IE. 356 | */ 357 | 358 | textarea { 359 | overflow: auto; 360 | } 361 | 362 | /** 363 | * 1. Add the correct box sizing in IE 10-. 364 | * 2. Remove the padding in IE 10-. 365 | */ 366 | 367 | [type="checkbox"], 368 | [type="radio"] { 369 | box-sizing: border-box; /* 1 */ 370 | padding: 0; /* 2 */ 371 | } 372 | 373 | /** 374 | * Correct the cursor style of increment and decrement buttons in Chrome. 375 | */ 376 | 377 | [type="number"]::-webkit-inner-spin-button, 378 | [type="number"]::-webkit-outer-spin-button { 379 | height: auto; 380 | } 381 | 382 | /** 383 | * 1. Correct the odd appearance in Chrome and Safari. 384 | * 2. Correct the outline style in Safari. 385 | */ 386 | 387 | [type="search"] { 388 | -webkit-appearance: textfield; /* 1 */ 389 | outline-offset: -2px; /* 2 */ 390 | } 391 | 392 | /** 393 | * Remove the inner padding and cancel buttons in Chrome and Safari on OS X. 394 | */ 395 | 396 | [type="search"]::-webkit-search-cancel-button, 397 | [type="search"]::-webkit-search-decoration { 398 | -webkit-appearance: none; 399 | } 400 | 401 | /** 402 | * 1. Correct the inability to style clickable types in iOS and Safari. 403 | * 2. Change font properties to `inherit` in Safari. 404 | */ 405 | 406 | ::-webkit-file-upload-button { 407 | -webkit-appearance: button; /* 1 */ 408 | font: inherit; /* 2 */ 409 | } 410 | 411 | /* Interactive 412 | ========================================================================== */ 413 | 414 | /* 415 | * Add the correct display in IE 9-. 416 | * 1. Add the correct display in Edge, IE, and Firefox. 417 | */ 418 | 419 | details, /* 1 */ 420 | menu { 421 | display: block; 422 | } 423 | 424 | /* 425 | * Add the correct display in all browsers. 426 | */ 427 | 428 | summary { 429 | display: list-item; 430 | } 431 | 432 | /* Scripting 433 | ========================================================================== */ 434 | 435 | /** 436 | * Add the correct display in IE 9-. 437 | */ 438 | 439 | canvas { 440 | display: inline-block; 441 | } 442 | 443 | /** 444 | * Add the correct display in IE. 445 | */ 446 | 447 | template { 448 | display: none; 449 | } 450 | 451 | /* Hidden 452 | ========================================================================== */ 453 | 454 | /** 455 | * Add the correct display in IE 10-. 456 | */ 457 | 458 | [hidden] { 459 | display: none; 460 | } -------------------------------------------------------------------------------- /dist/displace.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * displacejs.js 1.3.2 - Tiny javascript library to create moveable DOM elements. 3 | * Copyright (c) 2019 Catalin Covic - https://github.com/catc/displace 4 | * License: MIT 5 | */ 6 | (function webpackUniversalModuleDefinition(root, factory) { 7 | if(typeof exports === 'object' && typeof module === 'object') 8 | module.exports = factory(); 9 | else if(typeof define === 'function' && define.amd) 10 | define([], factory); 11 | else if(typeof exports === 'object') 12 | exports["displacejs"] = factory(); 13 | else 14 | root["displacejs"] = factory(); 15 | })(this, function() { 16 | return /******/ (function(modules) { // webpackBootstrap 17 | /******/ // The module cache 18 | /******/ var installedModules = {}; 19 | 20 | /******/ // The require function 21 | /******/ function __webpack_require__(moduleId) { 22 | 23 | /******/ // Check if module is in cache 24 | /******/ if(installedModules[moduleId]) 25 | /******/ return installedModules[moduleId].exports; 26 | 27 | /******/ // Create a new module (and put it into the cache) 28 | /******/ var module = installedModules[moduleId] = { 29 | /******/ exports: {}, 30 | /******/ id: moduleId, 31 | /******/ loaded: false 32 | /******/ }; 33 | 34 | /******/ // Execute the module function 35 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 36 | 37 | /******/ // Flag the module as loaded 38 | /******/ module.loaded = true; 39 | 40 | /******/ // Return the exports of the module 41 | /******/ return module.exports; 42 | /******/ } 43 | 44 | 45 | /******/ // expose the modules object (__webpack_modules__) 46 | /******/ __webpack_require__.m = modules; 47 | 48 | /******/ // expose the module cache 49 | /******/ __webpack_require__.c = installedModules; 50 | 51 | /******/ // __webpack_public_path__ 52 | /******/ __webpack_require__.p = ""; 53 | 54 | /******/ // Load entry module and return exports 55 | /******/ return __webpack_require__(0); 56 | /******/ }) 57 | /************************************************************************/ 58 | /******/ ([ 59 | /* 0 */ 60 | /***/ (function(module, exports, __webpack_require__) { 61 | 62 | 'use strict'; 63 | 64 | var _displace = __webpack_require__(1); 65 | 66 | var _displace2 = _interopRequireDefault(_displace); 67 | 68 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 69 | 70 | module.exports = _displace2.default; 71 | 72 | /***/ }), 73 | /* 1 */ 74 | /***/ (function(module, exports, __webpack_require__) { 75 | 76 | 'use strict'; 77 | 78 | Object.defineProperty(exports, "__esModule", { 79 | value: true 80 | }); 81 | 82 | var _utils = __webpack_require__(2); 83 | 84 | var _events = __webpack_require__(3); 85 | 86 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 87 | 88 | var moveFn = (0, _utils.generateMoveFn)(); 89 | 90 | var defaultOpts = { 91 | constrain: false, 92 | relativeTo: null, 93 | handle: null, 94 | ignoreFn: null, 95 | highlightInputs: false, 96 | 97 | // events 98 | onMouseDown: null, 99 | onMouseMove: null, 100 | onMouseUp: null, 101 | onTouchStart: null, 102 | onTouchMove: null, 103 | onTouchStop: null, 104 | 105 | customMove: null 106 | }; 107 | 108 | var Displace = function () { 109 | function Displace(el, opts) { 110 | _classCallCheck(this, Displace); 111 | 112 | if (!el) { 113 | throw Error('Must include moveable element'); 114 | } 115 | this.el = el; 116 | this.opts = opts; 117 | 118 | // init 119 | setup.call(this); 120 | } 121 | 122 | Displace.prototype.reinit = function reinit() { 123 | this.destroy(); 124 | setup.call(this); 125 | }; 126 | 127 | Displace.prototype.destroy = function destroy() { 128 | var events = this.events; 129 | 130 | this.handle.removeEventListener('mousedown', events.mousedown, false); 131 | document.removeEventListener('mousemove', events.mousemove, false); 132 | document.removeEventListener('mouseup', events.mouseup, false); 133 | 134 | this.handle.removeEventListener('touchstart', events.touchstart, false); 135 | document.removeEventListener('touchmove', events.touchmove, false); 136 | document.removeEventListener('touchstop', events.touchstop, false); 137 | document.removeEventListener('touchmove', this.events.scrollFix, { passive: false }); 138 | }; 139 | 140 | return Displace; 141 | }(); 142 | 143 | function setup() { 144 | var _this = this; 145 | 146 | var el = this.el; 147 | var opts = this.opts || defaultOpts; 148 | var data = {}; 149 | 150 | // set required css 151 | el.style.position = 'absolute'; 152 | 153 | // set the handle 154 | this.handle = opts.handle || el; 155 | 156 | // generate min / max ranges 157 | if (opts.constrain) { 158 | var relTo = opts.relativeTo || el.parentNode; 159 | 160 | var traverse = el; 161 | var minX = 0; 162 | var minY = 0; 163 | while (traverse !== relTo) { 164 | traverse = traverse.parentNode; 165 | if ((0, _utils.isRelative)(traverse)) { 166 | minX -= traverse.offsetLeft; 167 | minY -= traverse.offsetTop; 168 | } 169 | if (traverse === relTo) { 170 | minX += traverse.offsetLeft; 171 | minY += traverse.offsetTop; 172 | } 173 | } 174 | 175 | var maxX = minX + relTo.offsetWidth - el.offsetWidth; 176 | var maxY = minY + relTo.offsetHeight - el.offsetHeight; 177 | 178 | data.xClamp = (0, _utils.generateClamp)(minX, maxX); 179 | data.yClamp = (0, _utils.generateClamp)(minY, maxY); 180 | } 181 | 182 | this.opts = opts; 183 | this.data = data; 184 | this.events = { 185 | // mouse events 186 | mousedown: _events.mousedown.bind(this), 187 | mouseup: _events.mouseup.bind(this), 188 | 189 | // touch events 190 | touchstart: _events.touchstart.bind(this), 191 | touchstop: _events.touchstop.bind(this), 192 | 193 | // disable scrolling on mobile while dragging 194 | // https://github.com/bevacqua/dragula/issues/487 195 | scrollFix: function scrollFix(e) { 196 | if (_this.isDragging) { 197 | e.preventDefault(); 198 | } 199 | } 200 | }; 201 | 202 | // create move function - either use default move functionality or custom (if provided) 203 | this.handleMove = moveFn(this.opts.customMove); 204 | 205 | // add init events to handle 206 | this.handle.addEventListener('mousedown', this.events.mousedown, false); 207 | this.handle.addEventListener('touchstart', this.events.touchstart, false); 208 | 209 | // scroll fix for mobile 210 | document.addEventListener('touchmove', this.events.scrollFix, { passive: false }); 211 | } 212 | 213 | // export factory fn 214 | 215 | exports.default = function (el, opts) { 216 | return new Displace(el, opts); 217 | }; 218 | 219 | /***/ }), 220 | /* 2 */ 221 | /***/ (function(module, exports) { 222 | 223 | 'use strict'; 224 | 225 | Object.defineProperty(exports, "__esModule", { 226 | value: true 227 | }); 228 | exports.generateClamp = generateClamp; 229 | exports.isRelative = isRelative; 230 | exports.generateMoveFn = generateMoveFn; 231 | function generateClamp(min, max) { 232 | return function (val) { 233 | return Math.min(Math.max(val, min), max); 234 | }; 235 | } 236 | 237 | function isRelative(el) { 238 | return window.getComputedStyle(el).position === 'relative'; 239 | } 240 | 241 | function generateMoveFn() { 242 | if (window.requestAnimationFrame) { 243 | return function (customFn) { 244 | var move = customFn || defaultMove; 245 | 246 | return function (el, x, y) { 247 | window.requestAnimationFrame(function () { 248 | move(el, x, y); 249 | }); 250 | }; 251 | }; 252 | } 253 | return function (customFn) { 254 | return function (el, x, y) { 255 | var move = customFn || defaultMove; 256 | move(el, x, y); 257 | }; 258 | }; 259 | } 260 | 261 | function defaultMove(el, x, y) { 262 | el.style.left = x + 'px'; 263 | el.style.top = y + 'px'; 264 | } 265 | 266 | /***/ }), 267 | /* 3 */ 268 | /***/ (function(module, exports) { 269 | 270 | 'use strict'; 271 | 272 | Object.defineProperty(exports, "__esModule", { 273 | value: true 274 | }); 275 | exports.mousedown = mousedown; 276 | exports.mousemove = mousemove; 277 | exports.mouseup = mouseup; 278 | exports.touchstart = touchstart; 279 | exports.touchmove = touchmove; 280 | exports.touchstop = touchstop; 281 | // mouse events 282 | function mousedown(e) { 283 | var opts = this.opts; 284 | if (opts.highlightInputs) { 285 | // allow for selection of text in inputs/textareas 286 | var target = e.target.tagName.toLowerCase(); 287 | if (target === 'input' || target === 'textarea') { 288 | return; 289 | } 290 | } 291 | 292 | if (opts.ignoreFn && opts.ignoreFn(e)) { 293 | return; 294 | } 295 | 296 | // only left button is clicked 297 | if (e.button === 0) { 298 | var el = this.el; 299 | var events = this.events; 300 | 301 | if (typeof opts.onMouseDown === 'function') { 302 | opts.onMouseDown(el, e); 303 | } 304 | 305 | // determine initial offset and bind to mousemove handler 306 | var wOff = e.clientX - el.offsetLeft; 307 | var hOff = e.clientY - el.offsetTop; 308 | events.mousemove = mousemove.bind(this, wOff, hOff); 309 | 310 | document.addEventListener('mousemove', events.mousemove, false); 311 | document.addEventListener('mouseup', events.mouseup, false); 312 | } 313 | 314 | // prevent highlighting text when dragging (IE) 315 | e.preventDefault(); 316 | }; 317 | 318 | function mousemove(offsetW, offsetH, e) { 319 | var el = this.el; 320 | var opts = this.opts; 321 | var data = this.data; 322 | 323 | if (typeof opts.onMouseMove === 'function') { 324 | opts.onMouseMove(el, e); 325 | } 326 | 327 | var x = e.clientX - offsetW; 328 | var y = e.clientY - offsetH; 329 | 330 | if (opts.constrain) { 331 | // clamp values if out of range 332 | x = data.xClamp(x); 333 | y = data.yClamp(y); 334 | } 335 | this.handleMove(el, x, y); 336 | 337 | // prevent highlighting text when dragging 338 | e.preventDefault(); 339 | return false; 340 | }; 341 | 342 | function mouseup(e) { 343 | var el = this.el; 344 | var opts = this.opts; 345 | var events = this.events; 346 | 347 | if (typeof opts.onMouseUp === 'function') { 348 | opts.onMouseUp(el, e); 349 | } 350 | 351 | document.removeEventListener('mouseup', events.mouseup, false); 352 | document.removeEventListener('mousemove', events.mousemove, false); 353 | }; 354 | 355 | // touch events 356 | function touchstart(e) { 357 | var opts = this.opts; 358 | if (opts.highlightInputs) { 359 | // allow for selection of text in inputs/textareas 360 | var target = e.target.tagName.toLowerCase(); 361 | if (target === 'input' || target === 'textarea') { 362 | return; 363 | } 364 | } 365 | 366 | if (opts.ignoreFn && opts.ignoreFn(e)) { 367 | return; 368 | } 369 | 370 | var el = this.el; 371 | var events = this.events; 372 | 373 | if (typeof opts.onTouchStart === 'function') { 374 | opts.onTouchStart(el, e); 375 | } 376 | 377 | var touch = e.targetTouches[0]; 378 | var wOff = touch.clientX - el.offsetLeft; 379 | var hOff = touch.clientY - el.offsetTop; 380 | 381 | events.touchmove = touchmove.bind(this, wOff, hOff); 382 | 383 | // disable scrolling 384 | this.isDragging = true; 385 | 386 | document.addEventListener('touchmove', events.touchmove, false); 387 | document.addEventListener('touchend', events.touchstop, false); 388 | document.addEventListener('touchcancel', events.touchstop, false); 389 | }; 390 | 391 | function touchmove(offsetW, offsetH, e) { 392 | var el = this.el; 393 | var opts = this.opts; 394 | var data = this.data; 395 | 396 | if (typeof opts.onTouchMove === 'function') { 397 | opts.onTouchMove(el, e); 398 | } 399 | 400 | var touch = e.targetTouches[0]; 401 | var x = touch.clientX - offsetW; 402 | var y = touch.clientY - offsetH; 403 | 404 | if (opts.constrain) { 405 | // clamp values if out of range 406 | x = data.xClamp(x); 407 | y = data.yClamp(y); 408 | } 409 | this.handleMove(el, x, y); 410 | 411 | // prevent highlighting text when dragging 412 | e.preventDefault(); 413 | return false; 414 | }; 415 | 416 | function touchstop(e) { 417 | // re-enable scrolling 418 | this.isDragging = false; 419 | 420 | var el = this.el; 421 | var opts = this.opts; 422 | var events = this.events; 423 | 424 | if (typeof opts.onTouchStop === 'function') { 425 | opts.onTouchStop(el, e); 426 | } 427 | 428 | document.removeEventListener('touchmove', events.touchmove, false); 429 | document.removeEventListener('touchend', events.touchstop, false); 430 | document.removeEventListener('touchcancel', events.touchstop, false); 431 | }; 432 | 433 | /***/ }) 434 | /******/ ]) 435 | }); 436 | ; -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | displace.js 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |

displace.js

23 |

A minimal javascript library for creating moveable DOM elements.

24 | 25 | 31 | 32 | 46 |
47 | 48 | 49 |
50 |
51 |

Installation and Usage

52 | Install via npm: 53 |
54 | 55 |
# install
 56 | $ npm install --save displacejs
 57 | 
58 |
// reference
 59 | import displace from 'displacejs';
 60 | 
61 | 62 |
63 |

If not installing via npm or using a global browser version, get the latest dist/displace.min.js script. Include it in your html and reference it via window.displacejs.

64 |
65 | 66 |

67 | 68 |
69 |

Usage is simple, just initialize with an element and any options.

70 |
71 | 72 |
const element = document.querySelector('.element')
 73 | const d = displace(element, options);
 74 | 
75 |
76 | 77 | 78 |
79 |

API

80 |

displace(element, options)

81 |

Instantiates new displace instance on a DOM element.

82 | 83 |
84 |

Methods

85 |

displace.reinit()

86 |

Runs setup again. Useful when divs have been changed or resized.

87 |

displace.destroy()

88 |

Removes event listeners and destroys instance.

89 | 90 |

Options

91 |

options.constrain

92 |

Constrains element to its parent container.

93 |

94 | Type: boolean 95 | Default: false 96 |

97 | 98 |

options.relativeTo

99 |

Constrains element to the specified dome element. Requires options.constrain to be true.

100 |

101 | Type: DOM element 102 | Default: null 103 |

104 | 105 |

options.handle

106 |

Assigns a child element as the moveable handle for the parent element.

107 |

108 | Type: DOM element 109 | Default: null 110 |

111 | 112 |

options.highlightInputs

113 |

Allows you to highlight text in inputs and textareas by disabling drag events originating from those elements.

114 |

115 | Type: boolean 116 | Default: false 117 |

118 | 119 |

options.ignoreFn

120 |

Function that allows you to prevent dragging from an event. If the function returns true, the event will be ignored. Function called with: (event)

121 |

122 | Type: function 123 | Default: null 124 |

125 | 126 |

options.customMove

127 |

Function that overrides how x and y are being set on the displaced element as it's moved. Function called with: (element, newX, newY)

128 |

129 | Type: function 130 | Default: null 131 |

132 | 133 |

Events

134 | 135 |

Event triggered functions are called with two arguments: the DOM element being triggered and the original event.

136 | 137 |

options.onMouseDown

138 |

Function that is triggered when the DOM element is clicked.

139 |

140 | Type: function 141 | Default: null 142 |

143 | 144 |

options.onMouseMove

145 |

Function that is triggered when the DOM element is being moved.

146 |

147 | Type: function 148 | Default: null 149 |

150 | 151 |

options.onMouseUp

152 |

Function that is triggered when user mouse-ups the moveable DOM element.

153 |

154 | Type: function 155 | Default: null 156 |

157 | 158 |

options.onTouchStart

159 |

Function that is triggered when user initiates a touch start events on element.

160 |

161 | Type: function 162 | Default: null 163 |

164 | 165 |

options.onTouchMove

166 |

Function that is triggered when user moves the element via touch event.

167 |

168 | Type: function 169 | Default: null 170 |

171 | 172 |

options.onTouchStop

173 |

Function that is triggered when user ends touch event.

174 |

175 | Type: function 176 | Default: null 177 |

178 | 179 |
180 |
181 | 182 | 183 |
184 |

Demo

185 | 186 |

Basic usage

187 |
188 |
189 | 190 |
191 | 192 |
193 |

194 | 
195 | 					
196 | 197 |
198 | Box 1 199 |
200 | Box 2 201 |
202 | Box 3 203 | Drag me 204 |
205 |
206 |
207 |
208 |
209 | 210 |

Magnifier

211 |
212 |
213 | 214 | 215 |
216 |
217 | 218 |

219 | View code 220 | Photo by Anders Jildén 221 |

222 |
223 | 224 |

Sorting

225 |
226 |
227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 |
245 |
246 | 247 |
248 | View code 249 | 250 |
251 | 252 |

Align to grid (via customFn)

253 |
254 |
255 | 256 |
257 |
258 | 259 | View code 261 | 262 |
263 | 264 | 265 | 266 |
267 |
268 |

Tips

269 |

When styling your moveable element, it's best to set the position of the element as absolute and disable the user-select via css:

270 |
271 | 272 |
.your-element {
273 |     position: absolute;
274 | 
275 |     -webkit-user-select: none;
276 |     -moz-user-select: none;
277 |     -ms-user-select: none;
278 |     user-select: none;
279 | }
280 | 
281 | 282 |
283 |

If Chrome complains about: Unable to preventDefault inside passive event listener due to target being treated as passive. Add the appropriate touch action css: 284 |

285 |
286 | 287 |
.your-element {
288 |     touch-action: none;
289 | }
290 | 
291 | 292 |
293 |

See this issue 294 |

295 |
296 |
297 | 298 | 299 | 300 |
301 |

Contributing

302 |

303 | Contributing If you find any bugs, have feature requests, or would like to contribute - either send me a pull request or open a ticket and I'll do my best to follow up on it. 304 |

305 | Developed and maintained by Catalin Covic under the MIT license. 306 |

307 |
308 | 309 | 310 | 311 | --------------------------------------------------------------------------------