const boxScroll = document.querySelector('.box__list_scroll');
const boxImgs = document.querySelectorAll('.box__imgs > .box__img');
boxScroll.addEventListener('scroll', event => {
const sh = event.target.scrollHeight;
const h = event.target.offsetHeight;
const y = event.target.scrollTop;
const len = boxImgs.length;
const t = y / (sh - h);
const index = Math.floor((len - 1) * t);
boxImgs.forEach((img, i) => img.classList.toggle('__active', i === index));
});
json.sort(({ image: a }, { image: b }) => (!a && !b ? 0 : !a ? -1 : 1));
Что точнее setInterval и setTimeout?
Какая альтернатива лучше?
resize
в данном случае лучше использовать MediaQueryList: change event<form method="get" action="servis">
<select name="utm_brand">
<option disabled selected>Бранд</option>
<option value="919" data-brand="919">Бранд_1</option>
<option value="920" data-brand="920">Бранд_2</option>
<option value="922" data-brand="922">Бранд_3</option>
</select>
<select name="utm_city">
<option disabled selected>Ваш город</option>
<option value="1201" data-city="1201">Абакан</option>
<option value="1214" data-city="1214">Аксай</option>
<option value="1210" data-city="1210">Альметьевск</option>
<option value="1206" data-city="1206">Артём</option>
</select>
<button type="submit">Подобрать</button>
</form>
class Foo {
constructor() {
this.o = {
bar: `Привет`,
// baz: this.o.bar <- Ошибка, this.o еще не определен, т.к сначала выполняется правая часть выражения присваивания
};
// Здесь this.o уже определено, можно присвоить ему новое свойство
this.o.baz = this.o.bar + `, Мир!`;
}
}
console.log(new Foo().o.baz);
const x = { x: x }
{ x: x }
, но сама переменная x
еще не определена в этот момент, поэтому код выбросит ошибку Cannot access 'x' before initialization
function randomDelay(min = 250, max = 750) {
return new Promise((resolve) => {
const ms = Math.random() * (max - min) + min;
setTimeout(resolve, ms);
});
}
function downloadAll(urls, limit = 4) {
return new Promise((resolveAll, rejectAll) => {
const result = [];
const iter = urls.entries();
let fulfilled = 0;
const next = () => {
const { done, value } = iter.next();
if (done) {
if (fulfilled === urls.length) {
resolveAll(result);
return;
}
return;
}
const [index, url] = value;
const onFulfilled = (val) => {
result[index] = val;
fulfilled += 1;
next();
};
randomDelay()
.then(() => fetch(url))
.then(onFulfilled, rejectAll);
};
for (let i = 0, l = Math.min(limit, urls.length); i < l; i++) {
next();
}
});
}
const urls = Array.from(
{ length: 100 },
(_, i) => `https://jsonplaceholder.typicode.com/todos/${i + 1}`
);
(async () => {
const responses = await downloadAll(urls, 2);
const data = await Promise.all(responses.map((r) => r.json()));
console.log(data);
})();
function printProtoChain(obj) {
const chain = [];
while ((obj = Object.getPrototypeOf(obj))) {
chain.push(obj.constructor.name);
}
console.table(chain);
}
printProtoChain(document.body);
(index) | Value
0 'HTMLBodyElement'
1 'HTMLElement'
2 'Element'
3 'Node'
4 'EventTarget'
5 'Object'
дополнительно необходимо просматривать установленные поля родительских классов
console.dir(document.body)
, все унаследованные поля будут лежать в самом объекте. function pairwise(array) {
const result = [];
for (let i = 0; i < array.length; i += 2) {
result.push({ x: array[i], y: array[i + 1] });
}
return result;
}
const polydata = 'MULTIPOLYGON(((-61.2811499892424 -51.8650394644694,-61.3156095883879 -51.84003983505,-61.3451180293736 -51.836359409781,-61.345979 -51.8243193384298,-61.2230287013809 -51.8586600606699,-61.2380183615625 -51.8776,-61.2792595470359 -51.8659093831693,-61.2811499892424 -51.8650394644694)),((-59.6938009153582 -52.2130104476997,-59.6697 -52.2216295490501,-59.6888996395972 -52.2286288414282,-59.6929605766079 -52.2129007110094,-59.6938009153582 -52.2130104476997),(-59.7427025342573 -52.2157144418542,-59.7402989592584 -52.226169308217,-59.7414397191119 -52.2143487582055,-59.7427025342573 -52.2157144418542)),((-58.4328292075227 -52.102969,-58.4668596645304 -52.094089483711,-58.444380216144 -52.0871892532998,-58.4332186231022 -52.1024482278935,-58.4328292075227 -52.102969)))';
const polystr = polydata.replace(/MULTIPOLYGON|\(|\)|\s+|\.-/g, (ch) => {
if (ch === 'MULTIPOLYGON') return '';
else if (ch === '.-') return '.0,-';
else if (ch === '(') return '[';
else if (ch === ')') return ']';
else if (ch[0] === ' ') return ',';
});
const polyarray = JSON.parse(polystr).map(arr => arr.map(a => pairwise(a)));
const { shapes, holes } = polyarray.reduce((acc, val) => {
if (val.length === 1) {
acc.shapes.push(...val[0]);
}
else if (val.length > 1) {
acc.holes.push(...val[0]);
acc.shapes.push(...val.slice(1).flat());
}
return acc;
}, { shapes: [], holes: [] });
console.log('shapes =>', shapes);
console.log('holes =>', holes);
// replace-title.js
const fs = require('fs');
const path = require('path');
const { default: render } = require('dom-serializer');
const htmlparser2 = require('htmlparser2');
const { findOne, textContent } = htmlparser2.DomUtils;
const isTargetFile = (filePath) => /\.html/i.test(filePath);
const srcDir = path.resolve(__dirname, process.argv[2]);
const dstDir = path.resolve(__dirname, process.argv[3]);
function processFile(srcFilePath, dstFilePath) {
const inHtmlString = fs.readFileSync(srcFilePath, { encoding: 'utf8' });
const dom = htmlparser2.parseDocument(inHtmlString);
const titleElement = findOne((node) => node.type === 'tag' && node.name === 'title', dom.childNodes, true);
const h1Element = findOne((node) => node.type === 'tag' && node.name === 'h1', dom.childNodes, true);
if (titleElement) {
const titleText = textContent(titleElement).trim();
const h1Text = h1Element ? textContent(h1Element).trim() : '';
const titleTextNode = titleElement.childNodes[0];
if (titleTextNode) {
titleTextNode.data = h1Text ? `${titleText} | ${h1Text}` : titleText;
}
const outHtmlString = render(dom, { encodeEntities: 'utf8' });
fs.writeFileSync(dstFilePath, outHtmlString);
}
}
function processDir(dirPath) {
const files = fs.readdirSync(dirPath);
for (const file of files) {
const absFilePath = path.join(dirPath, file);
if (fs.statSync(absFilePath).isDirectory()) {
processDir(absFilePath);
} else if (isTargetFile(absFilePath)) {
const relPath = path.relative(srcDir, absFilePath);
const dstFilePath = path.join(dstDir, relPath);
const targetDir = path.dirname(dstFilePath);
if (!fs.existsSync(targetDir)) {
fs.mkdirSync(targetDir, { recursive: true });
}
processFile(absFilePath, dstFilePath);
}
}
}
processDir(srcDir);
{
"name": "replace-title",
"version": "1.0.0",
"description": "",
"main": "replace-title.js",
"license": "ISC",
"dependencies": {
"dom-serializer": "^2.0.0",
"htmlparser2": "^8.0.1"
}
}
npm install
node ./replace-title.js ./src ./dst
function headerScroll() {
let headerTop = null;
let headerSubstrate = null;
let lastScrollTop = 0;
function onScroll() {
let scrollDistance = window.scrollY;
if (scrollDistance > lastScrollTop) {
headerTop.classList.remove('header__top-active');
headerSubstrate.style.top = '-200px';
} else {
headerTop.classList.add('header__top-active');
headerSubstrate.style.top = '0px';
}
lastScrollTop = scrollDistance;
}
function init() {
destroy();
headerTop = document.querySelector('.header__top');
headerSubstrate = document.querySelector('.header__substrate');
lastScrollTop = 0;
window.addEventListener('scroll', onScroll);
}
function destroy() {
window.removeEventListener('scroll', onScroll);
}
return { init, destroy };
}
const { init: initStickyHeader, destroy: destroyStickyHeader } = headerScroll();
const mediaQuery = window.matchMedia('(min-width: 768px)');
mediaQuery.addEventListener('change', (event) => {
if (event.matches) {
initStickyHeader();
} else {
destroyStickyHeader();
}
});
- 26: return `<div class="lots">${lotsData.map((lotData) => Lot(lotData))}</div>`;
+ 26: return `<div class="lots">${lotsData.map((lotData) => Lot(lotData)).join('')}</div>`;