diff --git a/.parcel-cache/data.mdb b/.parcel-cache/data.mdb new file mode 100644 index 0000000..04c5415 Binary files /dev/null and b/.parcel-cache/data.mdb differ diff --git a/.parcel-cache/lock.mdb b/.parcel-cache/lock.mdb new file mode 100644 index 0000000..d2a78a4 Binary files /dev/null and b/.parcel-cache/lock.mdb differ diff --git a/README.md b/README.md index a7e88b8..ffbfb59 100644 --- a/README.md +++ b/README.md @@ -1 +1,7 @@ -[DEMO](https://dimadp.github.io/fe-template/) +# JS Developer Test + ### Example of Drag’n’Drop using mouse events. +## Project setup + - ### npm i + - ### npm start + - ### npm run build +# [DEMO LINK](https://dimadp.github.io/JS-soccer/) diff --git a/package-lock.json b/package-lock.json index 98b6f6f..5c610fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1741,9 +1741,9 @@ } }, "@linthtml/linthtml": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@linthtml/linthtml/-/linthtml-0.4.1.tgz", - "integrity": "sha512-R9OgrsJSm0s/QGaENFNrlpN3ZTxcy4z/zI25jkKN8ej9j+bWHvQERmZPoWln2L6h3tTUreooTbxEeKMaW+FPNA==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@linthtml/linthtml/-/linthtml-0.4.2.tgz", + "integrity": "sha512-GrYYtgBeO1KdugcdQodgYC04Iiqh08rn+YBhYhELHPs+DCEMfIfXn+DnSoPbR4HXml9kyMFUPJb3EwFzUBSrbg==", "dev": true, "requires": { "bulk-require": "1.0.1", @@ -1824,9 +1824,9 @@ } }, "cli-spinners": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.3.0.tgz", - "integrity": "sha512-Xs2Hf2nzrvJMFKimOR7YR0QwZ8fc0u98kdtwN1eNAZzNQgH3vK2pXzff6GJtKh7S5hoJ87ECiAiZFS2fb5Ii2w==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.4.0.tgz", + "integrity": "sha512-sJAofoarcm76ZGpuooaO0eDy8saEy+YoZBLjC4h8srt4jeBnkYeOgqxgsJQTpyt2LjI5PTfLJHSL+41Yu4fEJA==", "dev": true }, "color-convert": { @@ -2061,9 +2061,9 @@ "dev": true }, "onetime": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", - "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "requires": { "mimic-fn": "^2.1.0" @@ -2110,14 +2110,14 @@ "dev": true }, "parse-json": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", - "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", + "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, @@ -2209,9 +2209,9 @@ } }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -2452,9 +2452,9 @@ "dev": true }, "@types/glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-VgNIkxK+j7Nz5P7jvUZlRvhuPSmsEfS03b0alKcq5V/STUKAa3Plemsn5mrQUO7am6OErJ4rhGEGJbACclrtRA==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", "dev": true, "requires": { "@types/minimatch": "*", @@ -10074,6 +10074,12 @@ "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", "dev": true }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", @@ -12791,6 +12797,15 @@ "walker": "~1.0.5" } }, + "sass": { + "version": "1.26.10", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.26.10.tgz", + "integrity": "sha512-bzN0uvmzfsTvjz0qwccN1sPm2HxxpNI/Xa+7PlUEMS+nQvbyuEK7Y0qFqxlPHhiNHb1Ze8WQJtU31olMObkAMw==", + "dev": true, + "requires": { + "chokidar": ">=2.0.0 <4.0.0" + } + }, "sass-graph": { "version": "2.2.5", "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.5.tgz", diff --git a/package.json b/package.json index 663b8c4..6d4c0d7 100644 --- a/package.json +++ b/package.json @@ -30,12 +30,7 @@ "jest": "^26.0.1", "node-sass": "^4.14.1", "parcel": "^1.12.4", + "sass": "^1.26.10", "stylelint": "^13.6.1" - }, - "husky": { - "hooks": { - "pre-commit": "npm run lint", - "pre-push": "npm run lint" - } } } diff --git a/src/helpers.js b/src/helpers.js new file mode 100644 index 0000000..a2dfcd3 --- /dev/null +++ b/src/helpers.js @@ -0,0 +1,33 @@ +export const randomInteger = (min, max) => { + const rand = min + Math.random() * (max - min); + + return Math.floor(rand); +}; + +export const getCoords = (elem) => { + const box = elem.getBoundingClientRect(); + + return { + // eslint-disable-next-line no-undef + top: box.top + pageYOffset, + // eslint-disable-next-line no-undef + left: box.left + pageXOffset, + }; +}; + +export const disableSelection = (target) => { + if (typeof target.onselectstart !== 'undefined') { + target.onselectstart = function() { + return false; + }; + } else { + if (typeof target.style.MozUserSelect !== 'undefined') { + target.style.MozUserSelect = 'none'; + } else { + target.onmousedown = function() { + return false; + }; + target.style.cursor = 'default'; + } + } +}; diff --git a/src/index.html b/src/index.html index 2f4df5a..f840551 100644 --- a/src/index.html +++ b/src/index.html @@ -1,13 +1,38 @@ - - - - FE Template - - - + + + + JS Soccer + + + -

FE Template

+
+
+ In the left goal + +
+
+ SCORE + 0:0 +
+
+ In the right goal + +
+
+
+
+
+
+
+
+
+ - \ No newline at end of file + diff --git a/src/main.js b/src/main.js index e69de29..3bc08fd 100644 --- a/src/main.js +++ b/src/main.js @@ -0,0 +1,147 @@ +import { randomInteger, getCoords, disableSelection } from './helpers'; + +const field = document.body.querySelector('.field'); +const score = document.querySelector('.score__value'); +const leftGoalsList = document.querySelector('.info__goal--left .info__list'); +const rightGoalsList = document.querySelector('.info__goal--right .info__list'); +const fieldW = 1200; +const fieldH = 640; +const gateW = 150; +const gateH = 382; +const ballW = 42; + +const scoreValue = { + left: 0, + right: 0, +}; + +score.innerText = scoreValue.left + ' : ' + scoreValue.right; + +const balls = [['div', '#214BF2'], + ['p', '#E58F1F'], + ['b', '#EBFB10'], + ['a', '#E23FB7'], + ['i', '#75E23F'], + ['span', '#1CECBB']]; + +const minX = gateW; +const maxX = fieldW - gateW - ballW; +const minY = 0; +const maxY = fieldH - ballW; + + +balls.forEach(el => { + const element = document.createElement(el[0]); + + element.classList.add('ball', 'field__ball'); + element.innerText = el[0].toUpperCase(); + field.append(element); + element.style.position = 'absolute'; + element.style.backgroundColor = el[1]; + + const newX = randomInteger(minX, maxX); + const newY = randomInteger(minY, maxY); + + element.style.top = newY + 'px'; + element.style.left = newX + 'px'; +}); + +const ballList = document.querySelectorAll('.field__ball'); + +ballList.forEach(ball => { + ball.onmousedown = function(event) { + if (event.target.classList.contains('ball--dropped')) { + return; + }; + + const { top, left } = getCoords(field); + + const shiftX = event.clientX - ball.getBoundingClientRect().left; + const shiftY = event.clientY - ball.getBoundingClientRect().top; + + function moveAt(pageX, pageY) { + ball.style.left = pageX - left - shiftX + 'px'; + ball.style.top = pageY - top - shiftY + 'px'; + + if (ball.getBoundingClientRect().left < left) { + ball.style.left = 0 + 'px'; + }; + + if (ball.getBoundingClientRect().top < top) { + ball.style.top = 0 + 'px'; + }; + + if (ball.getBoundingClientRect() + .left > left + fieldW - ball.offsetWidth) { + ball.style.left = fieldW - ball.offsetWidth + 'px'; + }; + + if (ball.getBoundingClientRect().top > top + fieldH - ball.offsetHeight) { + ball.style.top = fieldH - ball.offsetHeight + 'px'; + }; + }; + + moveAt(event.pageX, event.pageY); + + function onMouseMove(e) { + moveAt(e.pageX, e.pageY); + }; + document.addEventListener('mousemove', onMouseMove); + + document.onmouseup = function() { + if (ball.getBoundingClientRect().left <= left + gateW - ball.offsetWidth + && ball.getBoundingClientRect().top >= top + gateW - ballW / 2 + && ball.getBoundingClientRect() + .top <= top + gateW - ballW / 2 + gateH - ball.offsetHeight) { + ball.classList.add('ball--dropped'); + goal('left'); + addGoalToTheList('left'); + } + + if (ball.getBoundingClientRect().left >= left + fieldW - gateW + && ball.getBoundingClientRect().top >= top + gateW - ballW / 2 + && ball.getBoundingClientRect() + .top <= top + gateW - ballW / 2 + gateH - ball.offsetHeight) { + ball.classList.add('ball--dropped'); + goal('right'); + addGoalToTheList('right'); + } + + document.removeEventListener('mousemove', onMouseMove); + document.onmouseup = null; + }; + }; + + ball.ondragstart = function() { + return false; + }; + + function goal(side) { + scoreValue[side]++; + score.innerText = scoreValue.left + ' : ' + scoreValue.right; + } + + function addGoalToTheList(side) { + const newElement = ball.cloneNode(true); + + newElement.style.position = 'static'; + + const listItem = document.createElement('li'); + + listItem.classList.add('info__item'); + listItem.append(newElement); + + switch (side) { + case 'left': + leftGoalsList.append(listItem); + break; + case 'right': + rightGoalsList.append(listItem); + break; + default: + break; + } + } +}); + +disableSelection(document.body); diff --git a/src/style.scss b/src/style.scss deleted file mode 100644 index 657123f..0000000 --- a/src/style.scss +++ /dev/null @@ -1,4 +0,0 @@ -h1 { - color: red; - background-color: blue; -} diff --git a/src/styles/_ball.scss b/src/styles/_ball.scss new file mode 100644 index 0000000..29adcbe --- /dev/null +++ b/src/styles/_ball.scss @@ -0,0 +1,17 @@ +.ball { + display: block; + margin: 0; + padding: 0; + font-style: normal; + font-weight: 600; + text-decoration: none; + width: 42px; + height: 42px; + + border-radius: 50%; + line-height: 42px; + font-size: 13px; + color: #fff; + text-align: center; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; +} diff --git a/src/styles/_field.scss b/src/styles/_field.scss new file mode 100644 index 0000000..5af4ee6 --- /dev/null +++ b/src/styles/_field.scss @@ -0,0 +1,61 @@ +.field { + background-color: #709B5E; + width: 1200px; + height: 640px; + margin: auto; + box-shadow: 0 21px 99px rgba(0,0,0,0.36); + border-radius: 20px; + position: relative; + + &__line { + width: 7px; + background-color: #BDCEB6; + height: 640px; + margin: auto; + box-sizing: border-box; + } + + &__circle--small { + position: absolute; + border-radius: 50%; + + width: 28px; + height: 28px; + background-color: #BDCEB6; + left: calc(50% - 14px); + top: calc(50% - 14px); + } + + &__circle--big { + box-sizing: border-box; + position: absolute; + border-radius: 50%; + + width: 176px; + height: 176px; + border: 7px solid #BDCEB6; + + left: calc(50% - 88px); + top: calc(50% - 88px); + } + + &__gate { + width: 150px; + height: 382px; + background-color: #BDCEB6; + + position: absolute; + top: calc(50% - (382px / 2)); + } + + &__gate--right { + right: 0; + } + + &__ball { + position: absolute; + top: 10%; + left: 60%; + cursor: pointer; + } +} diff --git a/src/styles/_info.scss b/src/styles/_info.scss new file mode 100644 index 0000000..4fbe1b1 --- /dev/null +++ b/src/styles/_info.scss @@ -0,0 +1,32 @@ +.info { + margin: auto; + width: 1200px; + display: flex; + justify-content: space-evenly; + font-family: 'Montserrat', sans-serif; + + &__score { + font-size: 54px; + display: flex; + flex-direction: column; + text-align: center; + width: 33%; + } + + &__goal { + margin-top: 20px; + font-size: 24px; + text-transform: uppercase; + width: 33%; + text-align: center; + } + + &__list { + display: flex; + } + + &__item { + margin-top: 10px; + margin-right: 5px; + } +} diff --git a/src/styles/style.scss b/src/styles/style.scss new file mode 100644 index 0000000..b631186 --- /dev/null +++ b/src/styles/style.scss @@ -0,0 +1,23 @@ +@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@600&display=swap'); +@import 'field'; +@import 'ball'; +@import 'info'; + +h1 { + color: red; + background-color: blue; +} + +li { + list-style-type: none; +} + +ul { + margin: 0; + padding: 0; + margin-left: 100px; + } + +.body { + user-select: none; +}