├── static └── .gitkeep ├── .eslintignore ├── test ├── unit │ ├── setup.js │ ├── .eslintrc │ ├── specs │ │ └── HelloWorld.spec.js │ └── jest.conf.js └── e2e │ ├── specs │ └── test.js │ ├── custom-assertions │ └── elementCount.js │ ├── nightwatch.conf.js │ └── runner.js ├── src ├── assets │ ├── logo.png │ ├── images │ │ ├── gif.gif │ │ ├── decrease.png │ │ └── increase.png │ └── style │ │ └── leftMove.less ├── directives │ ├── index.js │ └── leftMove.js ├── router │ └── index.js ├── App.vue ├── main.js ├── components │ ├── leftMove.vue │ └── HelloWorld.vue └── js │ └── util.js ├── config ├── prod.env.js ├── test.env.js ├── dev.env.js └── index.js ├── .editorconfig ├── .gitignore ├── .postcssrc.js ├── index.html ├── .babelrc ├── .eslintrc.js ├── package.json └── README.md /static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /config/ 3 | /dist/ 4 | /*.js 5 | /test/unit/coverage/ 6 | -------------------------------------------------------------------------------- /test/unit/setup.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | Vue.config.productionTip = false 4 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jacky-MYD/left-move/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"' 4 | } 5 | -------------------------------------------------------------------------------- /src/assets/images/gif.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jacky-MYD/left-move/HEAD/src/assets/images/gif.gif -------------------------------------------------------------------------------- /test/unit/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | }, 5 | "globals": { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/assets/images/decrease.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jacky-MYD/left-move/HEAD/src/assets/images/decrease.png -------------------------------------------------------------------------------- /src/assets/images/increase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jacky-MYD/left-move/HEAD/src/assets/images/increase.png -------------------------------------------------------------------------------- /config/test.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const devEnv = require('./dev.env') 4 | 5 | module.exports = merge(devEnv, { 6 | NODE_ENV: '"testing"' 7 | }) 8 | -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"' 7 | }) 8 | -------------------------------------------------------------------------------- /src/directives/index.js: -------------------------------------------------------------------------------- 1 | import LeftMove from './leftMove' 2 | 3 | function init(vue) { 4 | vue.directive('LeftMove', LeftMove); 5 | } 6 | 7 | export default { 8 | init: init 9 | } 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | /test/unit/coverage/ 8 | /test/e2e/reports/ 9 | selenium-debug.log 10 | 11 | # Editor directories and files 12 | .idea 13 | .vscode 14 | *.suo 15 | *.ntvs* 16 | *.njsproj 17 | *.sln 18 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | "postcss-import": {}, 6 | "postcss-url": {}, 7 | // to edit target browsers: use "browserslist" field in package.json 8 | "autoprefixer": {} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | left-move-delete 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /test/unit/specs/HelloWorld.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import HelloWorld from '@/components/HelloWorld' 3 | 4 | describe('HelloWorld.vue', () => { 5 | it('should render correct contents', () => { 6 | const Constructor = Vue.extend(HelloWorld) 7 | const vm = new Constructor().$mount() 8 | expect(vm.$el.querySelector('.hello h1').textContent) 9 | .toEqual('Welcome to Your Vue.js App') 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": ["transform-vue-jsx", "transform-runtime"], 12 | "env": { 13 | "test": { 14 | "presets": ["env", "stage-2"], 15 | "plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import HelloWorld from '../components/HelloWorld' 4 | import leftMove from '../components/leftMove' 5 | 6 | Vue.use(Router) 7 | 8 | export default new Router({ 9 | routes: [ 10 | { 11 | path: '/', 12 | name: 'leftMove', 13 | component: leftMove 14 | }, 15 | { 16 | path: '/HelloWorld', 17 | name: 'HelloWorld', 18 | component: HelloWorld 19 | } 20 | ] 21 | }) 22 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 28 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import 'babel-polyfill' 4 | import Vue from 'vue' 5 | import App from './App' 6 | import router from './router' 7 | import directives from './directives' 8 | 9 | Vue.config.productionTip = false 10 | directives.init(Vue); 11 | 12 | /* eslint-disable no-new */ 13 | new Vue({ 14 | el: '#app', 15 | router, 16 | components: { App }, 17 | template: '' 18 | }) 19 | -------------------------------------------------------------------------------- /test/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // For authoring Nightwatch tests, see 2 | // http://nightwatchjs.org/guide#usage 3 | 4 | module.exports = { 5 | 'default e2e tests': function (browser) { 6 | // automatically uses dev Server port from /config.index.js 7 | // default: http://localhost:8080 8 | // see nightwatch.conf.js 9 | const devServer = browser.globals.devServerURL 10 | 11 | browser 12 | .url(devServer) 13 | .waitForElementVisible('#app', 5000) 14 | .assert.elementPresent('.hello') 15 | .assert.containsText('h1', 'Welcome to Your Vue.js App') 16 | .assert.elementCount('img', 1) 17 | .end() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/unit/jest.conf.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | rootDir: path.resolve(__dirname, '../../'), 5 | moduleFileExtensions: [ 6 | 'js', 7 | 'json', 8 | 'vue' 9 | ], 10 | moduleNameMapper: { 11 | '^@/(.*)$': '/src/$1' 12 | }, 13 | transform: { 14 | '^.+\\.js$': '/node_modules/babel-jest', 15 | '.*\\.(vue)$': '/node_modules/vue-jest' 16 | }, 17 | testPathIgnorePatterns: [ 18 | '/test/e2e' 19 | ], 20 | snapshotSerializers: ['/node_modules/jest-serializer-vue'], 21 | setupFiles: ['/test/unit/setup'], 22 | mapCoverage: true, 23 | coverageDirectory: '/test/unit/coverage', 24 | collectCoverageFrom: [ 25 | 'src/**/*.{js,vue}', 26 | '!src/main.js', 27 | '!src/router/index.js', 28 | '!**/node_modules/**' 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /test/e2e/custom-assertions/elementCount.js: -------------------------------------------------------------------------------- 1 | // A custom Nightwatch assertion. 2 | // The assertion name is the filename. 3 | // Example usage: 4 | // 5 | // browser.assert.elementCount(selector, count) 6 | // 7 | // For more information on custom assertions see: 8 | // http://nightwatchjs.org/guide#writing-custom-assertions 9 | 10 | exports.assertion = function (selector, count) { 11 | this.message = 'Testing if element <' + selector + '> has count: ' + count 12 | this.expected = count 13 | this.pass = function (val) { 14 | return val === this.expected 15 | } 16 | this.value = function (res) { 17 | return res.value 18 | } 19 | this.command = function (cb) { 20 | var self = this 21 | return this.api.execute(function (selector) { 22 | return document.querySelectorAll(selector).length 23 | }, [selector], function (res) { 24 | cb.call(self, res) 25 | }) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parserOptions: { 6 | parser: 'babel-eslint' 7 | }, 8 | env: { 9 | browser: true, 10 | }, 11 | extends: [ 12 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention 13 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules. 14 | 'plugin:vue/essential', 15 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md 16 | 'standard' 17 | ], 18 | // required to lint *.vue files 19 | plugins: [ 20 | 'vue' 21 | ], 22 | // add your custom rules here 23 | rules: { 24 | // allow async-await 25 | 'generator-star-spacing': 'off', 26 | // allow debugger during development 27 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/e2e/nightwatch.conf.js: -------------------------------------------------------------------------------- 1 | require('babel-register') 2 | var config = require('../../config') 3 | 4 | // http://nightwatchjs.org/gettingstarted#settings-file 5 | module.exports = { 6 | src_folders: ['test/e2e/specs'], 7 | output_folder: 'test/e2e/reports', 8 | custom_assertions_path: ['test/e2e/custom-assertions'], 9 | 10 | selenium: { 11 | start_process: true, 12 | server_path: require('selenium-server').path, 13 | host: '127.0.0.1', 14 | port: 4444, 15 | cli_args: { 16 | 'webdriver.chrome.driver': require('chromedriver').path 17 | } 18 | }, 19 | 20 | test_settings: { 21 | default: { 22 | selenium_port: 4444, 23 | selenium_host: 'localhost', 24 | silent: true, 25 | globals: { 26 | devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port) 27 | } 28 | }, 29 | 30 | chrome: { 31 | desiredCapabilities: { 32 | browserName: 'chrome', 33 | javascriptEnabled: true, 34 | acceptSslCerts: true 35 | } 36 | }, 37 | 38 | firefox: { 39 | desiredCapabilities: { 40 | browserName: 'firefox', 41 | javascriptEnabled: true, 42 | acceptSslCerts: true 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/e2e/runner.js: -------------------------------------------------------------------------------- 1 | // 1. start the dev server using production config 2 | process.env.NODE_ENV = 'testing' 3 | 4 | const webpack = require('webpack') 5 | const DevServer = require('webpack-dev-server') 6 | 7 | const webpackConfig = require('../../build/webpack.prod.conf') 8 | const devConfigPromise = require('../../build/webpack.dev.conf') 9 | 10 | let server 11 | 12 | devConfigPromise.then(devConfig => { 13 | const devServerOptions = devConfig.devServer 14 | const compiler = webpack(webpackConfig) 15 | server = new DevServer(compiler, devServerOptions) 16 | const port = devServerOptions.port 17 | const host = devServerOptions.host 18 | return server.listen(port, host) 19 | }) 20 | .then(() => { 21 | // 2. run the nightwatch test suite against it 22 | // to run in additional browsers: 23 | // 1. add an entry in test/e2e/nightwatch.conf.js under "test_settings" 24 | // 2. add it to the --env flag below 25 | // or override the environment flag, for example: `npm run e2e -- --env chrome,firefox` 26 | // For more information on Nightwatch's config file, see 27 | // http://nightwatchjs.org/guide#settings-file 28 | let opts = process.argv.slice(2) 29 | if (opts.indexOf('--config') === -1) { 30 | opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js']) 31 | } 32 | if (opts.indexOf('--env') === -1) { 33 | opts = opts.concat(['--env', 'chrome']) 34 | } 35 | 36 | const spawn = require('cross-spawn') 37 | const runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' }) 38 | 39 | runner.on('exit', function (code) { 40 | server.close() 41 | process.exit(code) 42 | }) 43 | 44 | runner.on('error', function (err) { 45 | server.close() 46 | throw err 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /src/components/leftMove.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 29 | 65 | -------------------------------------------------------------------------------- /src/js/util.js: -------------------------------------------------------------------------------- 1 | const aFrame = window.requestAnimationFrame || 2 | window.webkitRequestAnimationFrame || 3 | window.mozRequestAnimationFrame || 4 | window.oRequestAnimationFrame || 5 | window.msRequestAnimationFrame || 6 | function(callback) { 7 | window.setTimeout(callback, 1000 / 60); 8 | } 9 | 10 | const _vendor = (function() { 11 | var vendors = ['t', 'webkitT', 'MozT', 'msT', 'OT'], 12 | transform, 13 | i = 0, 14 | l = vendors.length, 15 | _elementStyle = document.createElement('div').style 16 | 17 | for (; i < l; i++) { 18 | transform = vendors[i] + 'ransform'; 19 | if (transform in _elementStyle) return vendors[i].substr(0, vendors[i].length - 1); 20 | } 21 | return false; 22 | })(); 23 | var exp = { 24 | // 动画节流 25 | rafThrottle(fn) { 26 | let locked,context,args 27 | return function() { 28 | if(locked) return 29 | context = this 30 | args = arguments 31 | locked = true 32 | aFrame(()=>{ 33 | locked = false 34 | fn.apply(context, args) 35 | }) 36 | } 37 | }, 38 | closest(el, selector) { 39 | if(el.closest){ 40 | return el.closest(selector) 41 | } 42 | let matchesSelector = el.matches || el.webkitMatchesSelector || el.mozMatchesSelector || el.msMatchesSelector 43 | 44 | while (el) { 45 | if (matchesSelector.call(el, selector)) { 46 | break; 47 | } 48 | el = el.parentElement; 49 | } 50 | return el; 51 | }, 52 | aFrame: aFrame.bind(window), 53 | prefixStyle(style) { 54 | if (_vendor === false) return false; 55 | if (_vendor === '') return style; 56 | return _vendor + style.charAt(0).toUpperCase() + style.substr(1); 57 | }, 58 | 59 | } 60 | 61 | export default exp 62 | -------------------------------------------------------------------------------- /src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 85 | 86 | 96 | 97 | 98 | 114 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: 1.3.1 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | 5 | const path = require('path') 6 | 7 | module.exports = { 8 | dev: { 9 | 10 | // Paths 11 | assetsSubDirectory: 'static', 12 | assetsPublicPath: '/', 13 | proxyTable: {}, 14 | 15 | // Various Dev Server settings 16 | host: 'localhost', // can be overwritten by process.env.HOST 17 | port: 8090, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 18 | autoOpenBrowser: false, 19 | errorOverlay: true, 20 | notifyOnErrors: true, 21 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 22 | 23 | // Use Eslint Loader? 24 | // If true, your code will be linted during bundling and 25 | // linting errors and warnings will be shown in the console. 26 | useEslint: true, 27 | // If true, eslint errors and warnings will also be shown in the error overlay 28 | // in the browser. 29 | showEslintErrorsInOverlay: false, 30 | 31 | /** 32 | * Source Maps 33 | */ 34 | 35 | // https://webpack.js.org/configuration/devtool/#development 36 | devtool: 'cheap-module-eval-source-map', 37 | 38 | // If you have problems debugging vue-files in devtools, 39 | // set this to false - it *may* help 40 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 41 | cacheBusting: true, 42 | sourceMap: true, 43 | cssSourceMap: true 44 | }, 45 | 46 | build: { 47 | // Template for index.html 48 | index: path.resolve(__dirname, '../dist/index.html'), 49 | 50 | // Paths 51 | assetsRoot: path.resolve(__dirname, '../dist'), 52 | assetsSubDirectory: 'static', 53 | assetsPublicPath: '/', 54 | 55 | /** 56 | * Source Maps 57 | */ 58 | 59 | productionSourceMap: true, 60 | // https://webpack.js.org/configuration/devtool/#production 61 | devtool: '#source-map', 62 | 63 | // Gzip off by default as many popular static hosts such as 64 | // Surge or Netlify already gzip all static assets for you. 65 | // Before setting to `true`, make sure to: 66 | // npm install --save-dev compression-webpack-plugin 67 | productionGzip: false, 68 | productionGzipExtensions: ['js', 'css'], 69 | 70 | // Run the build command with an extra argument to 71 | // View the bundle analyzer report after build finishes: 72 | // `npm run build --report` 73 | // Set to `true` or `false` to always turn it on or off 74 | bundleAnalyzerReport: process.env.npm_config_report 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/assets/style/leftMove.less: -------------------------------------------------------------------------------- 1 | 2 | .shopping_page{ 3 | overflow: hidden; 4 | background: #f5f5f5; 5 | .header { 6 | font-family: PingFangTC-Regular; 7 | font-size: 20px; 8 | line-height: 50px; 9 | color: #303030; 10 | text-align: center; 11 | background: white; 12 | } 13 | .production_list { 14 | margin-bottom: 10px; 15 | background: white; 16 | .item_box { 17 | display: flex; 18 | margin: 0 15px; 19 | justify-content: space-between; 20 | line-height: 50px; 21 | } 22 | .delete_item { 23 | position: absolute; 24 | top: 0; 25 | right: -60px; 26 | width: 60px; 27 | height: 100%; 28 | line-height: 100px; 29 | font-family: PingFangSC-Regular; 30 | font-size: 16px; 31 | text-align: center; 32 | color: white; 33 | background: #FF7EA9; 34 | } 35 | } 36 | .continueAdd{ 37 | padding: 15px; 38 | color: #11BFF4; 39 | background: white; 40 | margin-top: 10px; 41 | } 42 | .bd_bottom { 43 | border-bottom: .5px solid #e8e8e8; 44 | } 45 | //overflow: hidden; 46 | //.content{ 47 | // background: #f5f5f5; 48 | // .mart{ 49 | // margin-top: 10px; 50 | // } 51 | // &-continueAdd{ 52 | // padding: 15px; 53 | // color: #11BFF4; 54 | // background: white; 55 | // margin-top: 10px; 56 | // } 57 | // .item_box { 58 | // background: white; 59 | // height: 100px; 60 | // } 61 | // .content-items-item-pick{ 62 | // font-family: PingFangSC-Light; 63 | // margin-left: 30px; 64 | // width: 255px; 65 | // overflow: hidden;/*超出部分隐藏*/ 66 | // white-space: nowrap;/*不换行*/ 67 | // text-overflow:ellipsis;/*超出部分省略号显示*/ 68 | // } 69 | // .content-items-box{ 70 | // //padding-bottom: 10px; 71 | // background: #f5f5f5; 72 | // .content-items{ 73 | // // padding-left: 15px; 74 | // // padding-right: 15px; 75 | // background: white; 76 | // &:nth-of-type(2){ 77 | // padding: 11px 15px; 78 | // } 79 | // } 80 | // .delete_item { 81 | // position: absolute; 82 | // top: 0; 83 | // right: -60px; 84 | // width: 60px; 85 | // height: 100%; 86 | // line-height: 100px; 87 | // font-family: PingFangSC-Regular; 88 | // font-size: 16px; 89 | // text-align: center; 90 | // color: white; 91 | // background: #FF7EA9; 92 | // } 93 | // } 94 | // .submitButtonBox{ 95 | // padding: 50px 15px 0; 96 | // background: white; 97 | // } 98 | // .font_col { 99 | // color: #333; 100 | // font-size: 14px; 101 | // font-family: PingFangSC-Regular; 102 | // } 103 | //} 104 | } 105 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "left-move-delete", 3 | "version": "1.0.0", 4 | "description": "A Vue.js project", 5 | "author": "Jacky", 6 | "private": true, 7 | "scripts": { 8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 9 | "start": "npm run dev", 10 | "unit": "jest --config test/unit/jest.conf.js --coverage", 11 | "e2e": "node test/e2e/runner.js", 12 | "test": "npm run unit && npm run e2e", 13 | "lint": "eslint --ext .js,.vue src test/unit test/e2e/specs", 14 | "build": "node build/build.js" 15 | }, 16 | "dependencies": { 17 | "babel-polyfill": "^6.26.0", 18 | "vue": "^2.5.2", 19 | "vue-router": "^3.0.1" 20 | }, 21 | "devDependencies": { 22 | "autoprefixer": "^7.1.2", 23 | "babel-core": "^6.22.1", 24 | "babel-eslint": "^8.2.1", 25 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 26 | "babel-jest": "^21.0.2", 27 | "babel-loader": "^7.1.1", 28 | "babel-plugin-dynamic-import-node": "^1.2.0", 29 | "babel-plugin-syntax-jsx": "^6.18.0", 30 | "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0", 31 | "babel-plugin-transform-runtime": "^6.22.0", 32 | "babel-plugin-transform-vue-jsx": "^3.5.0", 33 | "babel-preset-env": "^1.3.2", 34 | "babel-preset-stage-2": "^6.22.0", 35 | "babel-register": "^6.22.0", 36 | "chalk": "^2.0.1", 37 | "chromedriver": "^2.27.2", 38 | "copy-webpack-plugin": "^4.0.1", 39 | "cross-spawn": "^5.0.1", 40 | "css-loader": "^0.28.11", 41 | "eslint": "^4.15.0", 42 | "eslint-config-standard": "^10.2.1", 43 | "eslint-friendly-formatter": "^3.0.0", 44 | "eslint-loader": "^1.7.1", 45 | "eslint-plugin-import": "^2.7.0", 46 | "eslint-plugin-node": "^5.2.0", 47 | "eslint-plugin-promise": "^3.4.0", 48 | "eslint-plugin-standard": "^3.0.1", 49 | "eslint-plugin-vue": "^4.0.0", 50 | "extract-text-webpack-plugin": "^3.0.0", 51 | "file-loader": "^1.1.4", 52 | "friendly-errors-webpack-plugin": "^1.6.1", 53 | "html-webpack-plugin": "^2.30.1", 54 | "jest": "^22.0.4", 55 | "jest-serializer-vue": "^0.3.0", 56 | "less-loader": "^4.1.0", 57 | "nightwatch": "^0.9.12", 58 | "node-notifier": "^5.1.2", 59 | "optimize-css-assets-webpack-plugin": "^3.2.0", 60 | "ora": "^1.2.0", 61 | "portfinder": "^1.0.13", 62 | "postcss-import": "^11.0.0", 63 | "postcss-loader": "^2.0.8", 64 | "postcss-url": "^7.2.1", 65 | "rimraf": "^2.6.0", 66 | "selenium-server": "^3.0.1", 67 | "semver": "^5.3.0", 68 | "shelljs": "^0.7.6", 69 | "style-loader": "^0.23.1", 70 | "stylus-loader": "^3.0.2", 71 | "uglifyjs-webpack-plugin": "^1.1.1", 72 | "url-loader": "^0.5.8", 73 | "vue-jest": "^1.0.2", 74 | "vue-loader": "^13.3.0", 75 | "vue-style-loader": "^3.0.1", 76 | "vue-template-compiler": "^2.5.2", 77 | "vux-loader": "^1.2.9", 78 | "webpack": "^3.6.0", 79 | "webpack-bundle-analyzer": "^2.9.0", 80 | "webpack-dev-server": "^2.9.1", 81 | "webpack-merge": "^4.1.0" 82 | }, 83 | "engines": { 84 | "node": ">= 6.0.0", 85 | "npm": ">= 3.0.0" 86 | }, 87 | "browserslist": [ 88 | "> 1%", 89 | "last 2 versions", 90 | "not ie <= 8" 91 | ] 92 | } 93 | -------------------------------------------------------------------------------- /src/directives/leftMove.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacky on 2017-7-13. 3 | * 用于适配苹果安卓手机左滑取消关注商品,或者删除商品 4 | */ 5 | import util from '../js/util' 6 | const minX = -60; 7 | 8 | function TouchMove(el, bind) { 9 | this.el = el; 10 | this.bind = bind; 11 | this.startX = 0; 12 | this.startY = 0; 13 | this.x = 0; 14 | this.transformed = false; 15 | this.timer = null; 16 | 17 | if (bind.value.container) { 18 | this.closest = util.closest(el, bind.value.container) 19 | } 20 | this.touchstart = touchstart.bind(this) 21 | this.touchmove = util.rafThrottle(touchmove).bind(this) 22 | this.touchend = touchend.bind(this) 23 | this.bindEvents(); 24 | } 25 | 26 | TouchMove.prototype.bindEvents = function() { 27 | this.unbindEvents(); 28 | this.el.style.transform = 'translateX(0)'; 29 | this.el.addEventListener('touchstart', this.touchstart, false); 30 | this.el.addEventListener('touchmove', this.touchmove, false); 31 | this.el.addEventListener('touchend', this.touchend, false); 32 | }; 33 | 34 | TouchMove.prototype.unbindEvents = function() { 35 | this.el.style.transform = 'translateX(0)'; 36 | this.el.removeEventListener('touchstart', this.touchstart); 37 | this.el.removeEventListener('touchmove', this.touchmove); 38 | this.el.removeEventListener('touchend', this.touchend); 39 | }; 40 | 41 | TouchMove.prototype.setTransform = function(x) { 42 | this.el.style[util.prefixStyle('transform')] = 'translateX(' + x + 'px)'; 43 | } 44 | TouchMove.prototype.setTransition = function(isRemove) { 45 | if (isRemove) { 46 | this.el.style[util.prefixStyle('transition')] = ''; 47 | } else { 48 | this.el.style[util.prefixStyle('transition')] = 'transform .3s'; 49 | } 50 | } 51 | TouchMove.prototype.removeActive = function() { 52 | if (this.closest) { 53 | let old = this.closest.querySelector('[data-touchmove-active]') 54 | if (old && old._touchdel) { 55 | old._touchdel.x = 0; 56 | old._touchdel.setTransform(0); 57 | old.removeAttribute('data-touchmove-active'); 58 | } 59 | } 60 | }; 61 | TouchMove.prototype.reset = function() { 62 | this.el.style.translateX = "0px"; 63 | }; 64 | function touchstart(event) { 65 | this.startX = event.touches[0].pageX; 66 | this.startY = event.touches[0].pageY; 67 | this.setTransition(true); 68 | } 69 | function touchmove(event) { 70 | let endX = event.touches[0].pageX, 71 | endY = event.touches[0].pageY, 72 | diffX = endX - this.startX, 73 | diffY = endY - this.startY, 74 | temp = this.x + diffX; 75 | 76 | if (Math.abs(diffY) > Math.abs(diffX)) return; 77 | 78 | if (temp > 0) { 79 | temp = 0; 80 | } else if (temp < minX) { 81 | temp = minX; 82 | } 83 | this.x = Math.round(temp) 84 | this.setTransform(this.x) 85 | this.startX = endX 86 | } 87 | function touchend(event) { 88 | if (this.x < minX / 2) { 89 | this.x = minX; 90 | 91 | if (!this.el.dataset.touchmoveActive) { 92 | this.removeActive(); 93 | this.el.dataset.touchmoveActive = true; 94 | } 95 | this.el.classList.add('test'); 96 | } else { 97 | this.x = 0; 98 | this.el.removeAttribute('data-touchmove-active'); 99 | } 100 | this.setTransition(); 101 | this.setTransform(this.x); 102 | } 103 | 104 | export default { 105 | inserted(el, bind) { 106 | let touchdelt = new TouchMove(el,bind); 107 | 108 | el._touchdel = touchdelt; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # left-move-delete 2 | 3 | > A Vue.js project 4 | 5 | ``` bash 6 | # 商品列表、购物车或者关注商品等等 7 | 在移动端开发中,尤其是开发商城项目,可以用到该功能。 8 | 现在我们通过vue的指令(directives)来实现该功能,先看下面的效果图 9 | ``` 10 | ## 效果图 11 | ![Image text](https://github.com/Jacky-MYD/left-move/blob/master/src/assets/images/gif.gif)
12 | 13 | ```bash 14 | 首先我们先注册一个自定义指令 15 | import LeftMove from './leftMove' // 引入指令中的一些方法 16 | function init(vue) { 17 | vue.directive('LeftMove', LeftMove); 18 | } 19 | export default { 20 | init: init 21 | } 22 | 在main.js中全剧注册该指令 23 | import directives from './directives'; 24 | directives.init(Vue); 25 | 26 | leftMove.js中的方法 27 | 实例一个TouchMove对象,并且接收绑定的demo节点 28 | inserted(el, bind) { 29 | let touchdelt = new TouchMove(el,bind); 30 | el._touchdel = touchdelt; 31 | } 32 | 创建一个构造函数,并且监听绑定对象的touchstart, touchmove, touchend事件 33 | function TouchMove(el, bind) { 34 | this.el = el; 35 | this.bind = bind; 36 | this.startX = 0; 37 | this.startY = 0; 38 | this.x = 0; 39 | 40 | if (bind.value.container) { 41 | this.closest = util.closest(el, bind.value.container) 42 | } 43 | this.touchstart = touchstart.bind(this) 44 | this.touchmove = util.rafThrottle(touchmove).bind(this) 45 | this.touchend = touchend.bind(this) 46 | this.bindEvents(); 47 | } 48 | // 添加事件句柄 49 | TouchMove.prototype.bindEvents = function() { 50 | this.unbindEvents(); 51 | this.el.style.transform = 'translateX(0)'; 52 | this.el.addEventListener('touchstart', this.touchstart, false); 53 | this.el.addEventListener('touchmove', this.touchmove, false); 54 | this.el.addEventListener('touchend', this.touchend, false); 55 | }; 56 | // 移除事件句柄 57 | TouchMove.prototype.unbindEvents = function() { 58 | this.el.style.transform = 'translateX(0)'; 59 | this.el.removeEventListener('touchstart', this.touchstart); 60 | this.el.removeEventListener('touchmove', this.touchmove); 61 | this.el.removeEventListener('touchend', this.touchend); 62 | }; 63 | 64 | 当用户在移动demo节点的时候,监听横坐标的滑动距离 65 | function touchmove(event) { 66 | let endX = event.touches[0].pageX, 67 | endY = event.touches[0].pageY, 68 | diffX = endX - this.startX, 69 | diffY = endY - this.startY, 70 | temp = this.x + diffX; 71 | 72 | if (Math.abs(diffY) > Math.abs(diffX)) return; 73 | 74 | if (temp > 0) { 75 | temp = 0; 76 | } else if (temp < minX) { 77 | temp = minX; 78 | } 79 | this.x = Math.round(temp) 80 | this.setTransform(this.x) 81 | this.startX = endX 82 | } 83 | 通过用户移动的距离与设定按钮宽度的一半作比较,即minX/2, 84 | 当用户touchEnd时移动横坐标距离x < minX/2,则回弹到原始位置 85 | 当用户touchEnd时移动横坐标距离x > minX/2,则在该demo节点添加一个样式属性'data-touchmove-active',并且将该demo左滑置按钮的最大宽度,此时左滑成功,然后在改按钮 86 | 添加相应的事件(删除,关注,取消关注等) 87 | function touchend(event) { 88 | if (this.x < minX / 2) { 89 | this.x = minX; 90 | 91 | if (!this.el.dataset.touchmoveActive) { 92 | this.removeActive(); 93 | this.el.dataset.touchmoveActive = true; 94 | } 95 | this.el.classList.add('test'); 96 | } else { 97 | this.x = 0; 98 | this.el.removeAttribute('data-touchmove-active'); 99 | } 100 | this.setTransition(); 101 | this.setTransform(this.x); 102 | } 103 | 104 | 上述则是完整的指令执行顺序,下面我们看看页面绑定指令 105 | ``` 106 | ```js 107 | 我们在注册指令的时候,有注册LeftMove这个指令,所以我们直接在页面需要左滑删除的demo节点绑定v-leftMove即可, 108 | 然后有人就会问,为什么页面会的demo上有v-leftMove="{container: '[data-touchmove-con]'}"这么一段代码,其实这里是指定该指令只监听data-touchmove-con这个集合 109 | 中所绑定v-leftMove的元素。 110 | 111 | 此处就介绍完该指令的使用方法了,有不到之处,忘见谅! 112 | ``` 113 | 114 | For a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader). 115 | # left-move 116 | --------------------------------------------------------------------------------