I noticed that the offsetWidth of this.ratingSliderInput = document.querySelector(".js-rating-slider-input") changes randomly.
It goes from the real width to switching to 129 (no idea where that value is coming from).
This affects setting the position of this.ratingSliderThumb negatively.
Why does offsetWidth change to 129 randomly?
JavaScript:
class RatingSlider {
constructor() {
this.ratingSliderForm = document.querySelector(".js-rating-slider-form");
this.ratingSliderInput = document.querySelector(".js-rating-slider-input");
this.ratingSliderThumb = document.querySelector(".js-rating-slider-thumb");
this.ratingSliderValue = document.querySelector(".js-rating-slider-value");
this.ratingSliderIcon = document.querySelector(".js-rating-slider-icon");
this.isPressed = false;
this.setEvents();
this.bind();
}
setEvents() {
this.moveEvent;
this.startEvent;
this.endEvent;
if ("ontouchstart" in document.documentElement) {
this.moveEvent = "touchmove";
this.startEvent = "touchstart";
this.endEvent = "touchend";
} else {
this.moveEvent = "mousemove";
this.startEvent = "mousedown";
this.endEvent = "mouseup";
}
}
setThumbStyle() {
this.ratingSliderIcon.style.transform = `scale(${1 +
this.ratingSliderInput.value / 150})`;
this.ratingSliderValue.innerText = `${this.ratingSliderInput.value}°`;
}
setPositionThumb() {
this.ratingSliderThumb.style.left = `${(this.ratingSliderInput.offsetWidth /
100) *
this.ratingSliderInput.value -
10}px`;
}
handleOffsetOnChange(event) {
if ("ontouchstart" in document.documentElement) {
let touch = event.touches[0] || event.changedTouches[0];
let target = document.elementFromPoint(touch.clientX, touch.clientY);
event.offsetX = touch.clientX - target.getBoundingClientRect().x;
}
if (
event.offsetX > 0 &&
event.offsetX < this.ratingSliderInput.offsetWidth
) {
this.ratingSliderThumb.style.left = `${event.offsetX - 10}px`;
}
}
bind() {
if (!this.ratingSliderForm) {
return;
}
this.setPositionThumb();
this.setThumbStyle();
this.ratingSliderInput.addEventListener(
this.startEvent,
() => (this.isPressed = true)
);
this.ratingSliderInput.addEventListener(this.endEvent, () => {
this.isPressed = false;
this.ratingSliderForm.submit();
});
this.ratingSliderInput.addEventListener(this.moveEvent, (event) => {
if (!this.isPressed) {
return;
}
this.handleOffsetOnChange(event);
this.setThumbStyle();
});
}
}
export default RatingSlider;
CSS:
.rating-slider__inner-container {
position: relative;
padding-top: 100px;
}
.rating-slider__input {
-webkit-appearance: none;
width: 100%;
height: 5px;
background: $form-gray;
&:focus {
outline: none;
}
}
.rating-slider__input::-webkit-slider-thumb {
-webkit-appearance: none;
background: transparent;
border-color: transparent;
height: 20px;
width: 20px;
margin-top: -8px;
cursor: pointer;
}
.rating-slider__input::-moz-range-thumb {
background: transparent;
border-color: transparent;
height: 20px;
width: 20px;
cursor: pointer;
}
.rating-slider__input::-moz-focus-outer {
border: 0;
}
.rating-slider__thumb {
display: flex;
flex-direction: column;
align-items: center;
position: absolute;
top: 50px;
left: -10px;
font-size: $text-3xl;
pointer-events: none;
}
.rating-slider__value {
color: $brand-primary;
font-size: $text-lg;
}
.rating-slider__range-labels-container {
display: flex;
justify-content: space-between;
}
Here's the project live: https://wagon-city-guides.herokuapp.com/spots/32
And code on GitHub:
JS: https://github.com/mirhamasala/lw_city_guide/blob/master/app/javascript/components/rating_slider.js
CSS:
https://github.com/mirhamasala/lw_city_guide/blob/master/app/assets/stylesheets/components/_rating_slider.scss
HTML:
https://github.com/mirhamasala/lw_city_guide/blob/master/app/views/spots/_spot_rating.html.erb
I realized that your bug only appeared on fresh reload of the page, not coming from another and suspected turbolinks to be the culprit (as often...)
So I added this listener to your app/javascript/packs/application.js
window.onload = function() {
console.log("Window loaded")
let ratingSliderInput = document.querySelector(".js-rating-slider-input");
if(ratingSliderInput) {
console.log("setPositionThumb", ratingSliderInput)
console.log("setPositionThumb", ratingSliderInput.offsetWidth)
}
}
And a similar log in your setPositionThumb I get this:
I'm guessing that the event turbolinks:load may fire too early before the display is actually fully in place.
Related
When I look in the search engine for a word with an accent, for example: Débora, Lázaro, Ángela, Álvaro, Arquímedes, etc. does not find the result.
Without accents or with upper or lower case, the search engine works excellent.
Someone who can help me, I'm still a beginner in programming.
This is my complete code
const deepMerge = (...objects) => {
const isObject = (obj) => obj && typeof obj === "object";
return objects.reduce((prev, obj) => {
Object.keys(obj).forEach((key) => {
const pVal = prev[key];
const oVal = obj[key];
if (Array.isArray(pVal) && Array.isArray(oVal)) {
prev[key] = pVal.concat(...oVal);
} else if (isObject(pVal) && isObject(oVal)) {
prev[key] = deepMerge(pVal, oVal);
} else {
prev[key] = oVal;
}
});
return prev;
}, {});
};
const DEFAULT_OPTIONS = {
classNames: {
wrapperEl: "cselect",
selectEl: "cselect__select",
renderedEl: "cselect__rendered",
renderedTextEl: "cselect__rendered-text",
searchEl: "cselect__search",
optionsEl: "cselect__options",
optionEl: "cselect__option",
init: "js-init-cselect",
open: "is-open",
onTop: "is-on-top",
selected: "is-selected",
hidden: "is-hidden"
},
minimumOptionsForSearch: 10,
onOpen: null,
onClose: null,
onToggle: null
};
class CSelect {
// Elements
#wrapperEl;
#renderedEl;
#renderedTextEl;
#searchEl;
#optionsEl;
#optionEls;
// Functions
#handleSearch;
#optionElClick;
#clickOutside;
#escPress;
constructor(selectEl, options = {}) {
// Handle arguments
this.selectEl = selectEl;
this.options = deepMerge(DEFAULT_OPTIONS, options);
// Bind 'this'
this.open = this.open.bind(this);
this.close = this.close.bind(this);
this.toggle = this.toggle.bind(this);
this.#handleSearch = this.#handleSearchFn.bind(this);
this.#optionElClick = this.#optionElClickFn.bind(this);
this.#clickOutside = this.#clickOutsideFn.bind(this);
this.#escPress = this.#escPressFn.bind(this);
// Functions
this.init();
}
init() {
// Check if already init
if (this.selectEl.classList.contains(this.options.classNames.init)) {
console.error(`CSelect already initialized. ID: ${this.selectEl.id}`);
return;
}
// Handle select element
this.selectEl.setAttribute("tabindex", "-1");
this.selectEl.classList.add(this.options.classNames.selectEl);
// Functions
this.#generateHTML();
this.#addEvents();
// Add initialization
this.selectEl.classList.add(this.options.classNames.init);
}
#generateHTML() {
// Generate wrapper
const wrapperHTML = /* HTML */ `
<div class="${this.options.classNames.wrapperEl}"></div>
`;
this.selectEl.insertAdjacentHTML("beforebegin", wrapperHTML);
this.#wrapperEl = this.selectEl.previousElementSibling;
this.#wrapperEl.appendChild(this.selectEl);
// Generate rendered
const selectedOption = this.selectEl.options[this.selectEl.selectedIndex];
const selectedOptionText = selectedOption.textContent;
this.#renderedEl = document.createElement("button");
this.#renderedEl.type = "button";
this.#renderedEl.className = this.options.classNames.renderedEl;
this.#wrapperEl.appendChild(this.#renderedEl);
this.#renderedTextEl = document.createElement("span");
this.#renderedTextEl.className = this.options.classNames.renderedTextEl;
this.#renderedTextEl.textContent = selectedOptionText;
this.#renderedEl.appendChild(this.#renderedTextEl);
// Generate options wrapper
this.#optionsEl = document.createElement("div");
this.#optionsEl.className = this.options.classNames.optionsEl;
this.#wrapperEl.appendChild(this.#optionsEl);
// Generate search
if (
[...this.selectEl.options].length >= this.options.minimumOptionsForSearch
) {
this.#searchEl = document.createElement("input");
this.#searchEl.type = "text";
this.#searchEl.className = this.options.classNames.searchEl;
this.#optionsEl.appendChild(this.#searchEl);
}
// Generate each option
const selectOptions = [...this.selectEl.options];
this.#optionEls = [];
for (const option of selectOptions) {
if (option.disabled) {
continue;
}
const newOption = document.createElement("button");
newOption.type = "button";
newOption.className = this.options.classNames.optionEl;
newOption.textContent = option.textContent;
newOption.setAttribute("data-value", option.value);
if (option.selected) {
newOption.classList.add(this.options.classNames.selected);
}
this.#optionsEl.appendChild(newOption);
this.#optionEls.push(newOption);
}
}
open(callback) {
this.#wrapperEl.classList.add(this.options.classNames.open);
// Handle optionsEl position
this.#handleOptionsElPosition();
// Handle search
if (this.#searchEl !== null) {
this.#resetSearch();
this.#searchEl.focus();
}
// Handle callback functions
if (typeof this.options.onOpen === "function") {
this.options.onOpen(this);
}
if (typeof callback === "function") {
callback(this);
}
}
close(callback) {
this.#wrapperEl.classList.remove(this.options.classNames.open);
// Handle callback functions
if (typeof this.options.onClose === "function") {
this.options.onClose(this);
}
if (typeof callback === "function") {
callback(this);
}
}
toggle(callback) {
if (!this.#wrapperEl.classList.contains(this.options.classNames.open)) {
this.open();
} else {
this.close();
}
// Handle callback functions
if (typeof this.options.onToggle === "function") {
this.options.onToggle(this);
}
if (typeof callback === "function") {
callback(this);
}
}
#handleOptionsElPosition() {
this.#optionsEl.classList.remove(this.options.classNames.onTop);
const boundingRect = this.#optionsEl.getBoundingClientRect();
const isOutTop = boundingRect.top < 0;
const isOutBottom =
boundingRect.bottom >
(window.innerHeight || document.documentElement.clientHeight);
if (isOutBottom) {
this.#optionsEl.classList.add(this.options.classNames.onTop);
}
if (isOutTop) {
this.#optionsEl.classList.remove(this.options.classNames.onTop);
}
}
#resetSearch() {
this.#searchEl.value = "";
for (const optionEl of this.#optionEls) {
optionEl.classList.remove(this.options.classNames.hidden);
}
}
#handleSearchFn() {
for (const optionEl of this.#optionEls) {
if (
optionEl.textContent
.toLowerCase()
.indexOf(this.#searchEl.value.toLowerCase()) > -1
) {
optionEl.classList.remove(this.options.classNames.hidden);
} else {
optionEl.classList.add(this.options.classNames.hidden);
}
}
}
#optionElClickFn(event) {
// Close the select
this.close();
// Cache the target
const target = event.target;
// Check if click selected option
if (this.selectEl.value === target.dataset.value) {
return;
}
// Handle rendered text
this.#renderedTextEl.textContent = target.textContent;
// Handle select element change
this.selectEl.value = target.dataset.value;
const triggerEvent = new Event("change");
this.selectEl.dispatchEvent(triggerEvent);
// Highlight selected
for (const optionEl of this.#optionEls) {
optionEl.classList.remove(this.options.classNames.selected);
}
target.classList.add(this.options.classNames.selected);
}
#clickOutsideFn(event) {
const isOutside =
event.target.closest(`.${this.options.classNames.wrapperEl}`) !==
this.#wrapperEl;
const isOpen = this.#wrapperEl.classList.contains(
this.options.classNames.open
);
if (isOutside && isOpen) {
this.close();
}
}
#escPressFn(event) {
const isEsc = event.keyCode === 27;
const isOpen = this.#wrapperEl.classList.contains(
this.options.classNames.open
);
if (isEsc && isOpen) {
this.close();
}
}
#addEvents() {
this.#renderedEl.addEventListener("click", this.toggle);
if (this.#searchEl !== null) {
this.#searchEl.addEventListener("input", this.#handleSearch);
}
for (const optionEl of this.#optionEls) {
optionEl.addEventListener("click", this.#optionElClick);
}
document.addEventListener("click", this.#clickOutside);
document.addEventListener("keyup", this.#escPress);
}
destroy() {
// Check if already init
if (!this.selectEl.classList.contains(this.options.classNames.init)) {
console.error(`CSelect not initialized. ID: ${this.selectEl.id}`);
return;
}
// Remove Events
document.removeEventListener("click", this.#clickOutside);
document.removeEventListener("keyup", this.#escPress);
// Unwrap
this.#wrapperEl.replaceWith(this.selectEl);
// Clear select element
this.selectEl.removeAttribute("tabindex");
this.selectEl.classList.remove(this.options.classNames.selectEl);
this.selectEl.classList.remove(this.options.classNames.init);
}
}
// ***
window.addEventListener("DOMContentLoaded", () => {
window["selectObj"] = {};
const selectEls = [...document.querySelectorAll(".js-select")];
for (const selectEl of selectEls) {
window["selectObj"][selectEl.id] = new CSelect(selectEl, {
minimumOptionsForSearch: 0
// onClose: (cselectObj) => {
// console.log(cselectObj)
// },
});
}
// window['selectObj']['select1'].open((cselectObj) => {
// console.log(cselectObj)
// })
});
body {
margin: 0;
padding: 100px 150px;
font-family: sans-serif;
}
/* *** */
.cselect {
position: relative;
}
.cselect,
.cselect *,
.cselect *:before,
.cselect *:after {
box-sizing: border-box;
}
.cselect__select {
display: block;
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
z-index: -1;
pointer-events: none;
}
.cselect__rendered {
display: flex;
align-items: center;
justify-content: space-between;
margin: 0;
padding: 6px 12px;
width: 100%;
font: inherit;
font-size: 16px;
font-weight: normal;
color: #fff;
line-height: 1.5;
text-align: left;
text-decoration: none;
background: #333;
border: 0;
border-radius: 6px;
cursor: pointer;
}
.cselect__rendered:after {
content: "▾";
display: block;
margin: 0 0 0 8px;
}
.cselect.is-open .cselect__rendered:after {
content: "▴";
}
.cselect__rendered-text {
display: inline-block;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.cselect__options {
position: absolute;
top: 100%;
left: 0;
margin: 6px 0;
padding: 6px;
width: 100%;
max-height: 264px;
color: #fff;
background: #333;
border-radius: 6px;
transform: translateY(-6px) scale(0.98);
transform-origin: center top;
opacity: 0;
visibility: hidden;
transition-property: transform, visibility, opacity;
transition-duration: 0.3s;
transition-timing-function: ease;
overflow: auto;
z-index: 999;
}
.cselect__options::-webkit-scrollbar {
width: 4px;
}
.cselect__options::-webkit-scrollbar-track {
background: transparent;
border-radius: 4px;
}
.cselect__options::-webkit-scrollbar-thumb {
background: #555;
border-radius: 4px;
}
.cselect__options::-webkit-scrollbar-thumb:hover {
background: #777;
}
.cselect__options.is-on-top {
top: auto;
bottom: 100%;
}
.cselect.is-open .cselect__options {
transform: translateY(0) scale(1);
opacity: 1;
visibility: visible;
transition-property: transform, opacity;
}
.cselect__search {
display: block;
margin: 0 0 6px;
padding: 2px 6px;
width: 100%;
font: inherit;
font-size: 16px;
font-weight: normal;
color: #333;
line-height: 1.5;
background: #fff;
border: 0;
border-radius: 6px;
}
.cselect__option {
display: block;
padding: 6px;
width: 100%;
font: inherit;
font-size: 16px;
font-weight: normal;
color: #fff;
line-height: 1.5;
text-align: left;
text-decoration: none;
background: transparent;
border: 0;
border-radius: 6px;
cursor: pointer;
}
.cselect__option:hover {
background: #555;
}
.cselect__option.is-selected {
color: #999;
background: transparent;
cursor: default;
}
.cselect__option.is-hidden {
display: none;
}
<form action="./" method="get">
<button type="submit">GET</button>
<br><br><br><br>
<div style="max-width: 256px;">
<select name="select1" id="select1" class="js-select" required>
<option value="" selected disabled>Please select</option>
<option value="bunny">Perú</option>
<option value="kitten">Luís</option>
<option value="hamster">Hamster</option>
</select>
</div>
</form>
You need to update your function handleSearchFn to convert characters to the base ones (you can just replace yours with next one):
#handleSearchFn() {
const searchPhrase = this.#searchEl.value.normalize('NFD').replace(/[\u0300-\u036f]/g, "").toLowerCase();
for (const optionEl of this.#optionEls) {
if (optionEl.textContent.toLowerCase().indexOf(searchPhrase) > -1) {
optionEl.classList.remove(this.options.classNames.hidden);
} else {
optionEl.classList.add(this.options.classNames.hidden);
}
}
}
"230:45 Uncaught TypeError: Cannot read properties of undefined (reading 'shouldStopExecution')"
I got the following error can you fix it. I am trying to add HTML, CSS, and javascript on the same page.
I found this code in codepen i am trying to solve the issue but it's not working anymore...
But javascript not working here...
<div class="wrapper">
<div class="poll-box">
<div class="poll-container">
<div class="poll-question">Do You Love to Play FreeFire?</div>
<div class="poll-panel row mt-30">
<div class="btn poll-panel-btn" aria-role="button" data-result="0" data-vote="0"> <span>Yes</span></div>
<div class="btn poll-panel-btn" aria-role="button" data-result="0" data-vote="1"> <span>No</span></div>
</div>
</div>
</div>
</div>
<style>* {
box-sizing: border-box;
}
body {
background: #1b1b1b;
margin: 0;
}
.wrapper {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.mt-30 {
margin: 30px 0 0 0;
}
.row, .column {
display: flex;
}
.row {
flex-direction: row;
}
.column {
flex-direction: column;
}
.btn:not([disabled]) {
cursor: pointer;
}
.poll-box {
background: linear-gradient(#803af7, #500cc4);
border-radius: 3px;
box-shadow: 0px 5px 11px -7px #000;
text-align: center;
}
.poll-container {
padding: 25px 30px;
position: relative;
}
.poll-question {
width: max-content;
max-width: 700px;
color: #FFF;
font-family: "Poppins", sans-serif;
}
.poll-panel.poll-voted {
overflow: hidden;
border-radius: 50px;
}
.poll-panel.poll-voted .poll-panel-btn.--user-choice {
background: #FFF;
color: #000;
}
.poll-panel.poll-voted .poll-panel-btn.--user-choice:hover {
color: #000;
background: #FFF;
}
.poll-panel.poll-voted .poll-panel-btn {
background: #676464;
color: #FFF;
border-radius: 0;
margin: 0;
border: 0;
position: relative;
}
.poll-panel.poll-voted .poll-panel-btn:hover {
color: #FFF;
background: #676464;
}
.poll-panel.poll-voted .poll-panel-btn:after {
content: attr(data-result);
font-size: 9px;
display: block;
opacity: 0.5;
animation: slideWithFade 0.2s ease;
}
.poll-panel.poll-voted .poll-panel-btn:active {
transform: inherit;
}
.poll-panel.poll-voted .poll-panel-btn span {
display: block;
}
.poll-panel {
width: 100%;
}
.poll-panel-btn {
padding: 7px 10px;
font-family: "Roboto", sans-serif;
font-size: 0.7rem;
width: 100%;
border-radius: 50px;
border: 1px solid #FFF;
margin: 0 20px;
color: #FFF;
transition: 0.15s cubic-bezier(0.17, 0.67, 0.79, 1.24);
}
.poll-panel-btn:hover {
background: #FFF;
color: #000;
}
.poll-panel-btn:active {
transform: scale(0.95);
}
#keyframes slideWithFade {
0% {
opacity: 0;
transform: translateY(10px);
}
100% {
opacity: 1;
}
}</style>
<script>// POLL PLUGIN
class poll {
constructor(question, answers, options) {
const defaultOptions = {};
this.options = Object.assign({}, defaultOptions, options);
this.history = [];
this.possibleAnswers = answers;
}
clear() {
this.history = [];
}
get results() {
let numberOfVotes = this.history.length,
votesResults = [];
Object.keys(this.possibleAnswers).forEach(answerId => {
let answerIdCounter = 0;
let voters = [];
this.history.forEach(vote => {
if (answerId == vote.id) {
answerIdCounter++;
voters.push(vote.name);
}
});
let percentOfAllVotes = answerIdCounter / numberOfVotes * 100;
let formatedPercent = isNaN(percentOfAllVotes) ?
0 :
parseFloat(percentOfAllVotes).
toFixed(3).
slice(0, -1);
votesResults.push({
votes: answerIdCounter,
voters: voters,
percent: formatedPercent });
});
return votesResults;
}
vote(answerId, name = "Anonymouse") {
if (this.possibleAnswers[answerId]) {
let getCurrentDate = new Date().toLocaleString();
this.history.push({ id: answerId, name: name, date: getCurrentDate });
return true;
} else throw new Error("Incorrect answer's id");
}}
// Plugin: https://codepen.io/badurski/pen/RJvJQZ
const q1 = new poll("Will Poland win the footboal match?", {
0: { title: "Yes" },
1: { title: "No" } });
// Add some randome votes
for (let i = 0; i < 20; i++) {if (window.CP.shouldStopExecution(0)) break;
q1.vote(Math.floor(Math.random() * (1 - 0 + 1)) + 0);
}
// Poll interface script
window.CP.exitedLoop(0);let pollButtons = document.querySelectorAll('.poll-panel-btn'),
pollPanel = document.querySelector('.poll-panel');
pollButtons.forEach(button => {
button.onclick = () => {
if (button.getAttribute('disabled') != 'disabled') {
q1.vote(button.dataset.vote);
pollPanel.classList.add('poll-voted');
button.classList.add('--user-choice');
pollButtons.forEach(b => {
b.setAttribute('disabled', 'disabled');
let percent = q1.results[b.dataset.vote].percent + '%';
b.style.width = percent;
b.dataset.result = percent;
});
}
};
});</script>
In codepen everything is separated but in your HTML you should put the scripts before the divs.
Something like:
<script>
//blabla.yourscripts - you got it
</script>
<div class="wrapper">
<div class="poll-box">
<div class="poll-container">
<div class="poll-question">Do You Love to Play FreeFire?</div>
<div class="poll-panel row mt-30">
<div class="btn poll-panel-btn" aria-role="button" data-result="0" data-vote="0"> <span>Yes</span></div>
<div class="btn poll-panel-btn" aria-role="button" data-result="0" data-vote="1"> <span>No</span></div>
</div>
</div>
</div>
</div>
So just put the scripts before the stuff reading them.
You have included CP which is the CodePan component.
Please remove window.CP lines, then it will work:
window.CP.exitedLoop(0);
if (window.CP.shouldStopExecution(0)) break;
<script>
// POLL PLUGIN
class poll {
constructor(question, answers, options) {
const defaultOptions = {};
this.options = Object.assign({}, defaultOptions, options);
this.history = [];
this.possibleAnswers = answers;
}
clear() {
this.history = [];
}
get results() {
let numberOfVotes = this.history.length,
votesResults = [];
Object.keys(this.possibleAnswers).forEach(answerId => {
let answerIdCounter = 0;
let voters = [];
this.history.forEach(vote => {
if (answerId == vote.id) {
answerIdCounter++;
voters.push(vote.name);
}
});
let percentOfAllVotes = answerIdCounter / numberOfVotes * 100;
let formatedPercent = isNaN(percentOfAllVotes) ?
0 :
parseFloat(percentOfAllVotes).
toFixed(3).
slice(0, -1);
votesResults.push({
votes: answerIdCounter,
voters: voters,
percent: formatedPercent });
});
return votesResults;
}
vote(answerId, name = "Anonymouse") {
if (this.possibleAnswers[answerId]) {
let getCurrentDate = new Date().toLocaleString();
this.history.push({ id: answerId, name: name, date: getCurrentDate });
return true;
} else throw new Error("Incorrect answer's id");
}}
// Plugin: https://codepen.io/badurski/pen/RJvJQZ
const q1 = new poll("Will Poland win the footboal match?", {
0: { title: "Yes" },
1: { title: "No" } });
// Add some randome votes
for (let i = 0; i < 20; i++) {
q1.vote(Math.floor(Math.random() * (1 - 0 + 1)) + 0);
}
// Poll interface script
let pollButtons = document.querySelectorAll('.poll-panel-btn'),
pollPanel = document.querySelector('.poll-panel');
pollButtons.forEach(button => {
button.onclick = () => {
if (button.getAttribute('disabled') != 'disabled') {
q1.vote(button.dataset.vote);
pollPanel.classList.add('poll-voted');
button.classList.add('--user-choice');
pollButtons.forEach(b => {
b.setAttribute('disabled', 'disabled');
let percent = q1.results[b.dataset.vote].percent + '%';
b.style.width = percent;
b.dataset.result = percent;
});
}
};
});
</script>
I want to add 7 custom lines as helper to user.
Like in that picture, I want to add 7 times "div.moveable-line"
Even rotation change, the lines stayed at suitible position => And I want to add them 7 times.
Can we create a line between T1 and B1 (and for the others)?
Or if you have any other solutions, I am open for them as well.
React Moveable - Github
Warpable - StoryBook
Moveable.warpable - Documentation
Here is a demo link
MY COMPONENT
import React from 'react';
import ReactDOM from 'react-dom';
import Moveable from 'react-moveable';
import { ref } from 'framework-utils';
import { Frame } from 'scenejs';
import './styles.css';
class App extends React.Component {
frame = new Frame({
width: '250px',
height: '200px',
left: '0px',
top: '0px',
transform: {
rotate: '0deg',
scaleX: 1,
scaleY: 1,
matrix3d: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
},
});
state = {
target: null,
container: null,
warpable: true,
stateTransform: [],
totalBoxesTop: 0,
totalBoxesFill: 0,
totalBoxesBottom: 0,
isBoxCreated: false,
};
render() {
const { warpable, target } = this.state;
let k = document.querySelector('.moveable-control-box');
console.log(k, ':44');
if (k !== null) {
// k.appendChild(z);
// k.appendChild(d);
k.style.position = 'relative';
k.style.backgroundColor = '#fff';
}
let topLine = document.querySelector(
'.moveable-direction[data-line-index="0"]'
);
if (topLine !== null) {
topLine.classList.add('myTopLine');
let d = document.createElement('div'); // is a node
d.innerHTML = `T${this.state.totalBoxesTop}`;
d.setAttribute('data-box-position-top', `${this.state.totalBoxesTop}`);
d.classList.add('my-box');
if (this.state.totalBoxesTop < 8) {
// When is this.state.totalBoxes === 1 it means 0 boxes appear
topLine.appendChild(d);
this.setState({ totalBoxesTop: this.state.totalBoxesTop + 1 });
}
console.log(topLine, this.state.totalBoxesTop);
}
let bottomLine = document.querySelector(
'.moveable-direction[data-line-index="3"]'
);
if (bottomLine !== null) {
bottomLine.classList.add('myBottomLine');
let d = document.createElement('div'); // is a node
d.innerHTML = `B${this.state.totalBoxesBottom}`;
d.setAttribute(
'data-box-position-bottom',
`${this.state.totalBoxesBottom}`
);
d.classList.add('my-box');
if (this.state.totalBoxesBottom < 8) {
// When is this.state.totalBoxes === 1 it means 0 boxes appear
bottomLine.appendChild(d);
this.setState({ totalBoxesBottom: this.state.totalBoxesBottom + 1 });
}
console.log(bottomLine, this.state.totalBoxesBottom);
}
return (
<div className="page main">
<Moveable
ref={ref(this, 'moveable')}
target={target}
pinchThreshold={20}
container={document.body}
draggable={true}
warpable={warpable}
rotatable={true}
pinchable={true}
origin={false}
throttleDrag={1}
throttleRotate={0.2}
throttleResize={1}
throttleScale={0.01}
onDrag={this.onDrag}
onWarp={this.onWarp}
onDragEnd={this.onEnd}
onScaleEnd={this.onEnd}
onResizeEnd={this.onEnd}
onWarpEnd={this.onEnd}
onRotateEnd={this.onEnd}
onPinchEnd={this.onEnd}
/>
<div className="moveable">hello</div>
<div className="label" ref={ref(this, 'label')} />
</div>
);
}
componentDidMount() {
this.setState({
target: document.querySelector('.moveable'),
});
window.addEventListener('resize', this.onWindowReisze);
}
componentWillUnmount() {
window.removeEventListener('resize', this.onWindowReisze);
}
onWindowReisze = () => {
this.moveable.updateRect();
};
setTransform(target) {
target.style.cssText = this.frame.toCSS();
}
setLabel(clientX, clientY, text) {
this.label.style.cssText = `
display: block; transform: translate(${clientX}px, ${
clientY - 10
}px) translate(-100%, -100%) translateZ(-100px);`;
this.label.innerHTML = text;
}
onDrag = ({ target, clientX, clientY, top, left, isPinch }) => {
this.frame.set('left', `${left}px`);
this.frame.set('top', `${top}px`);
this.setTransform(target);
if (!isPinch) {
this.setLabel(clientX, clientY, `X: ${left}px<br/>Y: ${top}px`);
}
};
onWarp = ({
target,
clientX,
clientY,
delta,
multiply,
currentTarget,
moveable,
datas,
inputEvent,
transform,
dist,
matrix,
}) => {
console.log(target);
target.style.transform = `matrix3d(${matrix.join(',')})`;
this.setState({ stateTransform: `matrix3d(${matrix.join(',')})` });
this.frame.set(
'transform',
'matrix3d',
multiply(this.frame.get('transform', 'matrix3d'), delta)
);
this.setTransform(target);
this.setLabel(clientX, clientY, `X: ${clientX}px<br/>Y: ${clientY}px`);
};
onEnd = () => {
this.label.style.display = 'none';
};
}
export default App;
#import url("https://fonts.googleapis.com/css?family=Open+Sans:300,400,600&display=swap");
.moveable {
position: absolute;
width: 250px;
height: 200px;
margin: 0 auto;
background-color: transparent;
top: 0;
left: 0;
}
.my-new-box{
position: relative;
width: 100%;
height: 100%;
background-color: #73a079;
}
.myTopLine,
.myBottomLine{
background-color: #8b270a!important;
display: flex!important;
position: absolute!important;
justify-content: space-between!important;
align-items: flex-end!important;
}
.my-box {
position: relative;
top: 0;
left: 0;
width: 25px;
height: 25px;
/*flex: 1;*/
/*margin: 0 auto;*/
background-color: rgba(0,222,222,0.3);
/*transform: translate3d(42px, -62px, -135px);*/
}
.my-line{
position: relative;
top: 0;
left: 0;
width: 100px;
height: 150px;
background-color: #3a3aa0;
}
.moveable-control-box {
position: relative!important;
background-color: #8b2c62 !important;
}
.label {
position: fixed;
top: 0;
left: 0;
padding: 5px;
border-radius: 5px;
background: #333;
z-index: 3001;
color: #fff;
font-weight: bold;
font-size: 12px;
display: none;
transform: translate(-100%, -100%);
}
.feature .container .left {
position: relative;
width: 300px;
height: 205px;
display: inline-block;
vertical-align: top;
z-index: 2000;
margin-bottom: 20px;
}
.feature .container .right {
position: relative;
display: inline-block;
vertical-align: top;
flex: 1;
}
.feature .right .description {
text-align: left;
margin: 0px 0px 10px;
}
.feature .right .description strong {
font-weight: 600;
}
.draggable,
.resizable,
.scalable,
.rotatable,
.origin,
.warpable,
.pinchable {
position: absolute;
left: 0;
}
.origin {
transform-origin: 30% 50%;
}
pre {
position: relative;
border: 1px solid #ccc;
box-sizing: border-box;
padding: 10px;
max-width: 500px;
}
code.hljs {
padding: 0;
}
.tab {
padding: 10px 12px;
appearance: none;
-webkit-appearance: none;
background: transparent;
border: 1px solid #ccc;
box-shadow: none;
font-weight: bold;
margin: 0;
cursor: pointer;
outline: none;
}
.tab.selected {
background: #333;
color: #fff;
border: 1px solid #333;
}
.panel {
display: none;
}
.panel.selected {
display: block;
}
.page.footer {
font-weight: 400;
}
.page.footer a {
text-decoration: underline;
}
.page.footer span:first-child:before {
content: "";
}
.page.footer span:before {
content: "/";
}
Make Custom Able for Custom Lines
https://stackblitz.com/edit/react-ts-wmy77k?file=index.tsx
I want to make a draggle splitter between 2 panels. The following is a working version.
Now, I want to make the width of handle as thin as possible (less than 0.1px?), so there is no way to make the width (appear) smaller than 1px?
Additionally, when the splitter is thin, it is hard to select by the mouse. Is there a way to make a splitter easy to grab?
Taking JSBin as example, how did they manage to realise the splitters among the panels?
(function($) {
$.fn.drags = function(opt) {
opt = $.extend({
handle: "",
cursor: "ew-resize",
min: 10
}, opt);
if (opt.handle === "") {
var $el = this;
} else {
var $el = this.find(opt.handle);
}
var priorCursor = $('body').css('cursor');
return $el.css('cursor', opt.cursor).on("mousedown", function(e) {
priorCursor = $('body').css('cursor');
$('body').css('cursor', opt.cursor);
if (opt.handle === "") {
var $drag = $(this).addClass('draggable');
} else {
var $drag = $(this).addClass('active-handle').parent().addClass('draggable');
}
var z_idx = $drag.css('z-index'),
drg_h = $drag.outerHeight(),
drg_w = $drag.outerWidth(),
pos_y = $drag.offset().top + drg_h - e.pageY,
pos_x = $drag.offset().left + drg_w - e.pageX;
var mouseMove = function(e) {
var prev = $('.draggable').prev();
var next = $('.draggable').next();
var total = prev.outerWidth() + next.outerWidth();
var totalPercentage = parseFloat(prev.css('flex')) + parseFloat(next.css('flex'));
var offset = prev.offset();
if(offset){
var leftPercentage = ((e.pageX - offset.left - drg_w / 2) / total) * totalPercentage;
var rightPercentage = totalPercentage - leftPercentage;
if (leftPercentage * 100 < opt.min || rightPercentage * 100 < opt.min) {
return;
}
prev.css('flex', leftPercentage.toString());
next.css('flex', rightPercentage.toString());
}
}
$drag.css('z-index', 1000).parent().on("mousemove", mouseMove).on("mouseup", function() {
$(this).off("mousemove", mouseMove).off("mouseup");
$('body').css('cursor', priorCursor);
$('.draggable').removeClass('draggable').css('z-index', z_idx);
});
e.preventDefault(); // disable selection
});
}
})(jQuery);
$('.handle').drags();
.flex-box {
display: flex;
width: 100%;
margin: 0;
height: 300px;
}
.flex-box .col {
border: 1px solid grey;
flex: 0.33;
padding: 12px;
overflow-y: auto;
overflow-x: hide;
}
.handle {
width: 1px;
text-align: center;
background: grey;
transition: all ease-in 0.1s;
}
.draggable {
background: grey;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="flex-box">
<div class="col">
<p>Pellentesque ...</p>
</div>
<div class="handle"></div>
<div class="col">
<p>Pellentesque ...</p>
</div>
</div>
If you'd like the handle to appear thinner try applying a negative value to the right "col" e.g. margin-left: -2px; so it overlaps the left "col" border on the left of it. I don't think you can make the width "appear" as 0.1px. Firefox is the only browser that renders such value. (https://css-tricks.com/forums/topic/0-1px-borders/)
.flex-box .col:last-child {
margin-left: -2px;
}
//raise handle layer to top
.handle {
.....
z-index: 9999;
}
Hope this helps...
*Edit:
This is the closest I could get to your request:
.flex-box {
display: flex;
width: 100%;
margin: 0;
height: 300px;
}
.flex-box .col {
border: 1px solid grey;
flex: 0.33;
padding: 12px;
overflow-y: auto;
overflow-x: hide;
}
.flex-box .col:last-child {
margin-left: -6px;
}
.handle {
width: 5px;
text-align: center;
transition: all ease-in 0.1s;
z-index: 999;
overflow: visible;
}
.handle-inner{
width: 5px;
height: 100%;
position: relative;
margin-left: -10px;
}
.draggable {
background: grey;
}
Jsbin :
https://jsbin.com/nupefekuhu/edit?html,css,js,output
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 7 years ago.
Improve this question
Hello i've gotten this code from codepen and i was wondering if anybody could explain me how and where in the code the volume button is generated not the track but the megaphone symbol.
HTML
<h1>Youtube Volume Control</h1>
<div class='bsp-volume-wrap'>
<button id='bsp-volume'>
<span class='fa fa-volume-up'></span>
</button>
<div class='bsp-volume-panel'>
<div class='bsp-volume-slider'>
<div class='bsp-volume-slider-track'>
<div class='bsp-volume-slider-progress'>
<div class='bsp-volume-slider-handle'></div>
</div>
</div>
</div>
</div>
</div>
CSS
#import "//maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css";
#import "//fonts.googleapis.com/css?family=Roboto:700";
html, body {
font-family: 'Roboto', 'Helvetica Neue', Helvetica, Arial, sans-serif;
color: #333333;
text-align: center;
background: #1a1a1a;
}
h1 {
margin: 70px 0 50px;
font-size: 40px;
}
button {
background: none;
border: none;
color: #cccccc;
font-size: 20px;
padding: 0 10px;
height: 40px;
outline: none;
transition: color 0.2s;
cursor: pointer;
float: left;
}
button:hover {
color: white;
}
.bsp-volume-wrap {
padding: 0 10px 0 0;
display: inline-block;
}
.bsp-volume-wrap #bsp-volume {
float: left;
}
.bsp-volume-wrap.bsp-volume-show button {
color: white;
}
.bsp-volume-wrap.bsp-volume-show .bsp-volume-panel {
width: 73px;
}
.bsp-volume-wrap .bsp-volume-panel {
float: left;
height: 40px;
width: 0;
overflow: hidden;
transition: width 0.2s;
cursor: pointer;
}
.bsp-volume-wrap .bsp-volume-panel .bsp-volume-slider {
position: relative;
height: 100%;
}
.bsp-volume-wrap .bsp-volume-panel .bsp-volume-slider-track {
height: 2px;
width: 70px;
position: absolute;
top: 50%;
right: 0;
margin-top: -1px;
background: gray;
}
.bsp-volume-wrap .bsp-volume-panel .bsp-volume-slider-progress {
height: 100%;
width: 0%;
background: red;
position: relative;
}
.bsp-volume-wrap .bsp-volume-panel .bsp-volume-slider-handle {
height: 12px;
width: 3px;
position: absolute;
top: -5px;
right: 0;
background: white;
}
Javascript
(function () {
var VolumeControl, control, getElementPercentage, bind = function (fn, me) {
return function () {
return fn.apply(me, arguments);
};
};
getElementPercentage = function (click, elm) {
var rect;
rect = elm.getBoundingClientRect();
return (click.pageX - rect.left) / rect.width * 100;
};
VolumeControl = function () {
function VolumeControl() {
this.volumeMute = bind(this.volumeMute, this);
this.volumeStopHandler = bind(this.volumeStopHandler, this);
this.volumeMoveHandler = bind(this.volumeMoveHandler, this);
this.volumeDrag = bind(this.volumeDrag, this);
this.volumeClick = bind(this.volumeClick, this);
this.volumeHoverOut = bind(this.volumeHoverOut, this);
this.volumeHoverIn = bind(this.volumeHoverIn, this);
this.video = new Audio('http://garethweaver.com/codepen/media/bensound-jazzcomedy.mp3');
this.video.volume = 0;
this.video.play();
this.elm = {
volumeWrap: document.getElementsByClassName('bsp-volume-wrap')[0],
volumeSlider: document.getElementsByClassName('bsp-volume-slider')[0],
volumeProgress: document.getElementsByClassName('bsp-volume-slider-progress')[0]
};
this.elm.volumeWrap.addEventListener('mouseenter', this.volumeHoverIn);
this.elm.volumeWrap.addEventListener('mouseleave', this.volumeHoverOut);
this.elm.volumeSlider.addEventListener('click', this.volumeClick);
this.elm.volumeSlider.addEventListener('mousedown', this.volumeDrag);
document.getElementById('bsp-volume').addEventListener('click', this.volumeMute);
}
VolumeControl.prototype.volumeHoverIn = function (e) {
if (this.volumeHoverTimout) {
clearTimeout(this.volumeHoverTimout);
}
return this.elm.volumeWrap.classList.add('bsp-volume-show');
};
VolumeControl.prototype.volumeHoverOut = function (e) {
return this.volumeHoverTimout = setTimeout(function (_this) {
return function () {
return _this.elm.volumeWrap.classList.remove('bsp-volume-show');
};
}(this), 300);
};
VolumeControl.prototype.volumeClick = function (e) {
var percent;
percent = getElementPercentage(e, this.elm.volumeSlider);
return this.volumeSet(percent);
};
VolumeControl.prototype.volumeSet = function (percent) {
this.elm.volumeProgress.style.width = percent + '%';
return this.lastVolume = this.video.volume = percent / 100;
};
VolumeControl.prototype.volumeDrag = function (e) {
e.preventDefault();
document.addEventListener('mousemove', this.volumeMoveHandler);
return document.addEventListener('mouseup', this.volumeStopHandler);
};
VolumeControl.prototype.volumeMoveHandler = function (e) {
var percent;
percent = getElementPercentage(e, this.elm.volumeSlider);
if (percent < 0) {
percent = 0;
} else if (percent > 100) {
percent = 100;
}
return this.volumeSet(percent);
};
VolumeControl.prototype.volumeStopHandler = function (e) {
document.removeEventListener('mousemove', this.volumeMoveHandler);
return document.removeEventListener('mouseup', this.volumeStopHandler);
};
VolumeControl.prototype.volumeMute = function () {
var vol;
vol = this.video.volume > 0 ? 0 : this.lastVolume || 1;
this.video.volume = vol;
return this.elm.volumeProgress.style.width = vol * 100 + '%';
};
return VolumeControl;
}();
control = new VolumeControl();}.call(this));
For and easier way to read here is the link: http://codepen.io/garethdweaver/pen/EjqNxO
That is Font Awesome:
https://fortawesome.github.io/Font-Awesome/icons/
Go there and search (ctrl+F) for "volume".
Font Awesome is loaded on the first line of the CSS file:
#import "//maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css";
If you want to know more, visit the fa's home page: https://fortawesome.github.io/Font-Awesome/
edit:
Besides CSS, the HTML file needs to use this simple line to show the megaphone:
<span class='fa fa-volume-up'></span>