CSS mix blend mode / only white or black - css

Hello I'm currently trying to find a solution to make a sticky text black if the background is white or black in any other case. During my research I found mix blend mode property but it seems very complex to make what I want.
.sticky {
position: -webkit-sticky;
position: sticky;
top: 20px;
color:white;
font-size:60px;
mix-blend-mode: difference;
}
.panel {
height: 80vh;
widht: 100%;
}
.bg-black {
background: black;
}
.bg-red {
background: red;
}
.bg-blue {
background: blue;
}
.bg-green {
background: green;
}
<div class="sticky">
My text
</div>
<div>
<section class="panel"></section>
<section class="panel bg-black"></section>
<section class="panel bg-red"></section>
<section class="panel bg-blue"></section>
<section class="panel bg-green"></section>
</div>
Does someone know a hack or a package that can help me?
Thanks a lot

It is possible to make it with CSS only, by not applying blend mode to the sticky elements, but to the background(::before, ::after) items.
.bg-field::before, .bg-field::after {
background-color: white;
mix-blend-mode: difference;
pointer-events: none;
content: "";
position: absolute;
bottom: 0;
top: 0;
right: 0;
left: 0;
}
.bg-field::before {
z-index: 1;
}
.bg-field::after {
background-color: red;
z-index: -1;
}
Everything inside the bg field (or even outside if its a fixed element) will be colored
I made a code snippet displaying how it works: https://codepen.io/AndrewKnife/pen/XWzBpeN

I'm not sure it's possible with mix-blend-mode, can do what you want with filter and background-clip: text though:
// can ignore this, it's just making the sliders work as R G B
function updateColor() {
const r = document.getElementById('r').value;
const g = document.getElementById('g').value;
const b = document.getElementById('b').value;
document.querySelector('.container').style.background = `rgb(${r},${g},${b}`;
}
.container {
background: white;
}
.contrast-text {
font-size: 50vmin;
background: inherit;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
filter:
sepia(5) /* add some color to grey so the rest works */
saturate(100) /* increase strength of color channels */
invert(1) /* invert the color */
grayscale(1) /* make it grey */
contrast(9); /* make it black/white */
}
<input type="range" onchange="updateColor()" min=0 max=255 value=255 id="r">
<input type="range" onchange="updateColor()" min=0 max=255 value=255 id="g">
<input type="range" onchange="updateColor()" min=0 max=255 value=255 id="b">
<div class="container">
<div class="contrast-text"> Text </div>
</div>

I finally find something great ! It's not as beautiful as mix-blend-mode but it do the job.
I'd prefer to stay 100% css because it require use of ScrollMagic
const controller = new ScrollMagic.Controller();
const sections = document.querySelectorAll('section');
const menu = document.querySelector('.my-text');
sections.forEach((section, index, arr) => {
const trigger = '#' + section.id;
const backgroundColor = window.getComputedStyle(section, null).getPropertyValue('background-color');
const textColor = getContrastYIQ(backgroundColor);
let previousBackgroundColor = backgroundColor;
let previousTextColor = getContrastYIQ(previousBackgroundColor);
if (index >= 1) {
previousBackgroundColor = window.getComputedStyle(arr[index - 1], null).getPropertyValue('background-color');
previousTextColor = getContrastYIQ(previousBackgroundColor);
}
new ScrollMagic.Scene({
triggerElement: trigger,
triggerHook: "onLeave",
offset: -50,
reverse: true
})
.on("enter", function() {
menu.classList.remove(previousTextColor);
menu.classList.add(textColor);
})
.on("leave", function() {
menu.classList.remove(textColor); menu.classList.add(previousTextColor);
})
.addTo(controller);
})
// Color contrast helper function
// https://en.wikipedia.org/wiki/YIQ
function getContrastYIQ(rgb) {
rgb = rgb.substring(4, rgb.length - 1)
.replace(/ /g, '')
.split(',');
const yiq = ((rgb[0] * 299) + (rgb[1] * 587) + (rgb[2] * 114)) / 1000;
return (yiq >= 128) ? 'black' : 'white';
}
section {
min-height: 80vh;
}
.my-text {
position: sticky;
top: 5vh;
color: white;
}
.black {
color: black;
&:before {
background: black;
box-shadow: 0 0.4em 0 0 black, 0 0.80em 0 0 black;
}
}
#s1 {
background-color: black;
}
#s2 {
background-color: white;
}
#s3 {
background-color: #111;
}
#s4 {
background-color: #9f3;
}
#s5 {
background-color: #145;
}
#s6 {
background-color: #f5f;
}
<script ></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ScrollMagic/2.0.5/ScrollMagic.min.js"></script>
<div class="my-text">
MY TEXT</div>
<section id="s1">
</section>
<section id="s2"></section>
<section id="s3"></section>
<section id="s4"></section>
<section id="s5"></section>
<section id="s6"></section>

Related

How to Add a Space in Line Numbers in Textarea Word Wrap in Svelte

I have a text area with CSS generated line numbers for each new line.
Everything works great, except for when there is a word wrap. If I get a word wrap, it does not wrap the new line text. I would like to make my line numbers word wrap with the text.
Here is a notepad+ example:
And here is a GitHub example:
How can I automatically detect and add a word wrap to my line numbers as well? Here is my code:
<script lang="ts">
export let source = `jon
sup
me
fixin to do sumthin good`;
let numberOfLines = source.split('\n').length;
const KeyUp = (event: Event) => {
const textarea = event.target as HTMLInputElement;
numberOfLines = textarea.value.split('\n').length;
};
const KeyDown = (event: Event) => {
const textarea = event.target as HTMLInputElement;
if ((event as KeyboardEvent).key === 'Tab') {
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
if (start && end) {
textarea.value = textarea.value.substring(0, start) + '\t'
+ textarea.value.substring(end);
event.preventDefault();
}
}
};
</script>
<center>
<div class="editor">
<div class="line-numbers">
{#each Array(numberOfLines) as _}
<span class="new-line" />
{/each}
</div>
<textarea class="text-content"
on:keyup={KeyUp} on:keydown={KeyDown} value={source} rows="15" />
</div>
</center>
<style>
.editor {
display: inline-flex;
gap: 10px;
font-family: monospace;
line-height: 21px;
background: #282a3a;
border-radius: 2px;
padding: 20px 10px;
}
.line-numbers {
width: 20px;
text-align: right;
}
.line-numbers .new-line {
counter-increment: linenumber;
}
.line-numbers .new-line::before {
content: counter(linenumber);
display: block;
color: #506882 !important;
}
.text-content {
line-height: 21px;
overflow-y: hidden;
padding: 0;
border: 0;
background: #282a3a;
color: #fff;
min-width: 500px;
outline: none;
resize: none;
}
</style>
And here is the StackBlitz Link:
(right click - open in new window)
Thanks!
J
Image for responding to comment below:
An approach could be to read the width of one character of the monospace font from an invisible element and set the textarea width to a multiple to avoid rounding errors. It then can be calculated for each substring between the line breaks how often it is wrapped and a margin-bottom added to the corresponding line number accordingly
REPL
<script>
import {onMount} from 'svelte'
export let source = `jon\nsup there is some other text here that I want to go across the lines and not word wrap another line etc boy!\nme\nfixin to do sumthin good`;
let fontWidthElem
let fontWidth
let charactersPerLine = 60
$: areaWidth = charactersPerLine * fontWidth
onMount(() => {
fontWidth = fontWidthElem.getBoundingClientRect().width
})
function wrapped(str, timesWrapped) {
if(str.length > charactersPerLine) {
const firstLine = str.substring(0, charactersPerLine)
const spaceOrDashInFirstLine = /[ -]/.test(firstLine)
const lastCharFirstLine = str[charactersPerLine-1]
const firstCharNextLine = str[charactersPerLine]
const wordIsCut = lastCharFirstLine !== '-' && lastCharFirstLine !== ' ' && firstCharNextLine !== ' ' && firstCharNextLine !== '-'
const nextLine = str.substring(charactersPerLine)
const nextLineNotOnlySpaces = nextLine.replaceAll(' ', '').length > 0
if(wordIsCut && spaceOrDashInFirstLine){
const lastIndexOfDivider = Math.max(firstLine.lastIndexOf(' '), firstLine.lastIndexOf('-'))
return wrapped(str.substring(lastIndexOfDivider+1), timesWrapped+1)
}else if(nextLineNotOnlySpaces){
return wrapped(nextLine.trimStart(), timesWrapped+1)
}else {
return timesWrapped
}
}else {
return timesWrapped
}
}
</script>
<div class="editor">
<div class="line-numbers">
{#each source.split('\n') as subStr, index}
<div class="line-nr"
style:margin-bottom="calc(var(--line-height) * {wrapped(subStr, 0)})"
>
{index + 1}
</div>
{/each}
</div>
<textarea bind:value={source}
style:width="{areaWidth}px"
rows="15"
/>
<div id="font-width"
bind:this={fontWidthElem}
>
a
</div>
</div>
<style>
:global(body) {
padding: 0;
}
.editor {
--line-height: 21px;
position: relative;
display: inline-flex;
gap: 10px;
padding: 20px 10px;
font-family: monospace;
line-height: var(--line-height);
background: #282a3a;
border-radius: 2px;
}
.line-nr {
width: 20px;
text-align: right;
color: #506882 !important;
}
textarea {
line-height: var(--line-height);
overflow-y: hidden;
margin: 0;
padding: 0;
border: 0;
background: #282a3a;
color: #fff;
outline: none;
resize: none;
}
#font-width {
position: absolute;
top: -1000px;
left: -1000px;
visibility: hidden;
}
</style>
To make the width responsive
additional editor wrapper for padding
watch editor width and calculate maximal possible charactersPerLine for textarea besides line numbers element
charactersPerLine added as argument to wrapped() for recalculation on change
REPL
<script>
import {onMount} from 'svelte'
export let source = `jon\nsup there is some other text here that I want to go across the lines and not word wrap another line etc boy!\nme\nfixin to do sumthin good`;
let fontWidthElem
let fontWidth
let editorWidth
onMount(() => {
fontWidth = fontWidthElem.getBoundingClientRect().width
})
let lineNumbersWidth = 30
$: charactersPerLine = Math.floor((editorWidth - lineNumbersWidth) / fontWidth)
function wrapped(str, charactersPerLine, timesWrapped) {
if(str.length > charactersPerLine) {
const firstLine = str.substring(0, charactersPerLine)
const spaceOrDashInFirstLine = /[ -]/.test(firstLine)
const lastCharFirstLine = str[charactersPerLine-1]
const firstCharNextLine = str[charactersPerLine]
const wordIsCut = lastCharFirstLine !== '-' && lastCharFirstLine !== ' ' && firstCharNextLine !== ' ' && firstCharNextLine !== '-'
const nextLine = str.substring(charactersPerLine)
const nextLineNotOnlySpaces = nextLine.replaceAll(' ', '').length > 0
if(wordIsCut && spaceOrDashInFirstLine){
const lastIndexOfDivider = Math.max(firstLine.lastIndexOf(' '), firstLine.lastIndexOf('-'))
return wrapped(str.substring(lastIndexOfDivider+1), charactersPerLine, timesWrapped+1)
}else if(nextLineNotOnlySpaces){
return wrapped(nextLine.trimStart(), charactersPerLine, timesWrapped+1)
}else {
return timesWrapped
}
}else {
return timesWrapped
}
}
</script>
<div class="editor-wrapper">
<div class="editor"
bind:clientWidth={editorWidth}
>
<div class="line-numbers"
style:width="{lineNumbersWidth}px"
>
{#each source.split('\n') as subStr, index}
<div class="line-nr"
style:margin-bottom="calc(var(--line-height) * {wrapped(subStr, charactersPerLine, 0)})"
>
{index + 1}
</div>
{/each}
</div>
<textarea bind:value={source}
style:width="{charactersPerLine}ch"
rows="15"
/>
<div id="font-width"
bind:this={fontWidthElem}
>
a
</div>
</div>
</div>
<style>
:global(body) {
padding: 0;
}
:global(*) {
box-sizing: border-box;
}
.editor-wrapper {
--line-height: 21px;
padding: 20px 10px;
background: #282a3a;
border-radius: 2px;
}
.editor {
position: relative;
display: flex;
width: 100%;
font-family: monospace;
line-height: var(--line-height);
}
.line-numbers {
padding-right: 10px;
text-align: right;
color: #506882 !important;
}
textarea {
line-height: var(--line-height);
overflow-y: hidden;
margin: 0;
padding: 0;
border: 0;
background: #282a3a;
color: #fff;
outline: none;
resize: none;
/* border seems to falsify calculation! */
/* box-shadow: 0 0 0 1px grey; */
}
#font-width {
position: absolute;
top: -1000px;
left: -1000px;
visibility: hidden;
}
</style>

How can I showing truncated text only on hover without change height of box list?

My script of vue component like this :
<template>
...
<b-card-group deck v-for="row in formattedClubs">
<b-card v-for="club in row"
img-src="http://placehold.it/130?text=No-image"
img-alt="Img"
img-top>
<h4 class="card-title"
#mouseenter="club.truncate = false"
#mouseleave="club.truncate = true">
<template v-if="club.truncate">{{trucateText(club.description)}}</template>
<template v-else>{{club.description}}</template>
</h4>
<p class="card-text">
{{club.price}}
</p>
<p class="card-text">
{{club.country}}
</p>
<div slot="footer">
<b-btn variant="primary" block>Add</b-btn>
</div>
</b-card>
</b-card-group>
...
</template>
<script>
export default {
data: function () {
return {
clubs: [
{id:1, description:'chelsea is the best club in the world and chelsea has a great player', price:1000, country:'england'},
{id:2, description:'liverpool has salah', price:900, country:'england'},
{id:3, description:'mu fans', price:800, country:'england'},
{id:4, description:'city has a great coach. Thas is guardiola', price:700, country:'england'},
{id:5, description:'arsenal player', price:600, country:'england'},
{id:6, description:'tottenham in london', price:500, country:'england'},
{id:7, description:'juventus stadium', price:400, country:'italy'},
{id:8, description:'madrid sell ronaldo', price:300, country:'spain'},
{id:9, description:'barcelona in the spain', price:200, country:'spain'},
{id:10, description:'psg buys neymar at a fantastic price', price:100, country:'france'}
]
}
},
computed: {
formattedClubs() {
return this.clubs.reduce((c, n, i) => {
if (i % 4 === 0) c.push([]);
c[c.length - 1].push(n);
this.$set(n, 'truncate', true);
return c;
}, []);
}
},
methods: {
trucateText (value) {
const length = 30;
return value.length <= length ? value : value.substring(0, length) + "...";
}
}
}
</script>
If the script executed, the view like this :
If I hover the description, the result like this :
It change height of box list
How can I solve this problem?
I want the view like this :
We can see that you're using bootstrap-vue. Nice, so you can use v-b-tooltip directive and let itself control the hover behavior. As you don't need to track it by yourself anymore for each club, you can remove that reative property from your computed property formattedClubs:
this.$set(n, 'truncate', true); // Remove this line.
Now, update your template to use the directive only if the truncation is needed:
<h4 class="card-title"
v-if="club.description.length > 30"
v-b-tooltip.hover.bottom
:title="club.description">
{{trucate(club.description)}}
</h4>
<h4 v-else>{{club.description}}</h4>
And, of course, you can now style it the way you want overriding the right Boostrap styles:
.tooltip.show {
opacity: 1;
}
.tooltip-inner {
background: #fff;
color: #000;
padding: .5em 1em;
border: 1px solid #bbb;
box-shadow: 0 3px 8px rgba(0, 0, 0, .15);
}
.tooltip.bs-tooltip-auto[x-placement^=bottom] .arrow, .tooltip.bs-tooltip-bottom .arrow {
position: relative;
background: #fff;
top: 1px;
width: 16px;
}
.tooltip.bs-tooltip-auto[x-placement^=bottom] .arrow::before, .tooltip.bs-tooltip-bottom .arrow::before, .tooltip.bs-tooltip-auto[x-placement^=bottom] .arrow::after, .tooltip.bs-tooltip-bottom .arrow::after {
bottom: 0;
left: 50%;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
}
.tooltip.bs-tooltip-auto[x-placement^=bottom] .arrow::after, .tooltip.bs-tooltip-bottom .arrow::after {
border-color: rgba(255, 255, 255, 0);
border-bottom-color: #fff;
border-width: 8px;
margin-left: -8px;
}
.tooltip.bs-tooltip-auto[x-placement^=bottom] .arrow::before, .tooltip.bs-tooltip-bottom .arrow::before {
border-color: rgba(187, 187, 187, 0);
border-bottom-color: #bbb;
border-width: 9px;
margin-left: -9px;
}
Take a look at the fully working sampe here if you want.
I would simply wrap your truncated text with a tooltip component. You can pass the full text into this component as a prop.
On hover you can show the tooltip by using #mouseover and remove it by using #mouseleave.
The tooltip itself can be a position absolute element with a max-width. I am not going to type all of this out, but this should get you started.

How to fix v-align styling for imgs with different sizes in Vue.js transition-group?

I prepared example of image slider what I need.
I encounter with styling issue when using images with various dimensions. When element leaving the array, its location is set as absolute value, what is necessary for smooth transition, tho, but the image is also moved up.
I would like to have nice vertical align into middle even leave or enter the array, but could not get on any way.
Another issue, what I would like to solve is when left the window and then went back after a while. The animation running all cycles at once to reach current state instead just stop animation and run after. Maybe it is my responsibility, but browsers doesn't offer nice event to catch blur window or am I wrong?
According to this discussion
Thank you for any ideas.
let numbers = [{key:1},{key:2},{key:3},{key:4},{key:5},{key:6},{key:7}]
let images = [
{ key:1,
src:"http://lorempixel.com/50/100/sports/"},
{ key:2,
src:"http://lorempixel.com/50/50/sports/"},
{ key:3,
src:"http://lorempixel.com/100/50/sports/"},
{ key:4,
src:"http://lorempixel.com/20/30/sports/"},
{ key:5,
src:"http://lorempixel.com/80/20/sports/"},
{ key:6,
src:"http://lorempixel.com/20/80/sports/"},
{ key:7,
src:"http://lorempixel.com/100/100/sports/"}
]
new Vue({
el: '#rotator',
data: {
items: images,
lastKey: 7,
direction: false
},
mounted () {
setInterval(() => {
if (this.direction) { this.prevr() } else { this.nextr() }
}, 1000)
},
methods: {
nextr () {
let it = this.items.shift()
it.key = ++this.lastKey
this.items.push(it)
},
prevr () {
let it = this.items.pop()
it.key = ++this.lastKey
this.items.unshift(it)
}
}
})
.litem {
transition: all 1s;
display: inline-block;
margin-right: 10px;
border: 1px solid green;
background-color: lightgreen;
padding: 10px 10px 10px 10px;
height: 100px;
}
.innerDiv {
border: 1px solid red;
}
.container {
overflow: hidden;
white-space: nowrap;
height: 250px;
border: 1px solid blue;
background-color: lightblue;
}
.list-enter {
opacity: 0;
transform: translateX(40px);
}
.list-leave-to {
opacity: 0;
transform: translateX(-40px);
}
.list-leave-active {
position: absolute;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.11/vue.js"></script>
<div id="rotator">
<button #click="direction = !direction">
change direction
</button>
<transition-group
name="list"
tag="div"
class="container">
<div
v-for="item in items"
:key="item.key" class="litem">
<!--
<div
class='innerDiv'>
{{ item.key }}
</div>
-->
<div class='innerDiv'>
<img :src='item.src'>
</div>
</div>
</transition-group>
</div>
It tooks a while but on the end I think that I have got better result for sliding animation with changing direction feature.
One annoying think is when I swithing the sliding direction so animation is for a 'microsecond' changed to next state and than return to correct one, after it the animation continue as expected. It is happening only in one direction and I don't know how to fix it. Also last box behave differently too only once. No clue at all.
So just 98% solution :-)
let images = [
{key:1, domKey:1, src:"http://lorempixel.com/50/100/sports/" },
{key:2, domKey:2, src:"http://lorempixel.com/50/50/sports/" },
{key:3, domKey:3, src:"http://lorempixel.com/100/50/sports/" },
{key:4, domKey:4, src:"http://lorempixel.com/20/30/sports/" },
{key:5, domKey:5, src:"http://lorempixel.com/80/20/sports/" },
{key:6, domKey:6, src:"http://lorempixel.com/20/80/sports/" },
{key:7, domKey:7, src:"http://lorempixel.com/100/100/sports/" }
]
let setPositionRelative = el => el.style.position = "relative"
new Vue({
el: '#rotator',
data: {
items: images,
lastKey: 7,
direction: true,
changeDirectionRequest: false
},
mounted () {
Array.from(this.$el.querySelectorAll("div[data-key]")).map(setPositionRelative)
setInterval(() => {
if(this.changeDirectionRequest) {
this.changeDirectionRequest = false
this.direction = !this.direction
if (this.direction)
Array.from(this.$el.querySelectorAll("div[data-key]")).map(setPositionRelative)
else
Array.from(this.$el.querySelectorAll("div[data-key]")).map(el => el.style.position = "")
}
if (this.direction) this.prevr()
else this.nextr()
}, 1000)
},
methods: {
nextr () {
let it = this.items.shift()
it.key = ++this.lastKey
this.items.push(it)
},
prevr () {
let it = this.items.pop()
it.key = ++this.lastKey
this.items.unshift(it)
setPositionRelative(this.$el.querySelector("div[data-domkey='"+it.domKey+"']"))
}
}
})
.container {
overflow: hidden;
white-space: nowrap;
height: 200px;
border: 1px solid blue;
background-color: lightblue;
display: flex;
align-items: center;
}
.innerDiv {
border: 1px solid red;
width: auto;
height: auto;
display:-moz-box;
-moz-box-pack:center;
-moz-box-align:center;
display:-webkit-box;
-webkit-box-pack:center;
-webkit-box-align:center;
display:box;
box-pack:center;
box-align:center;
}
.litem {
transition: all 1s;
margin-right: 10px;
border: 1px solid green;
background-color: lightgreen;
padding: 10px 10px 10px 10px;
}
.list2-enter, .list-enter {
opacity: 0;
transform: translateX(40px);
}
.list2-leave-to, .list-leave-to {
opacity: 0;
transform: translateX(-40px);
}
.list-leave-active {
position: absolute;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.11/vue.js"></script>
<div id="rotator">
<button #click="changeDirectionRequest = true">change direction</button>
<transition-group name="list" tag="div" class="container">
<div v-for="item in items"
:key="item.key"
:data-domkey="item.domKey"
class="litem">
<div class='innerDiv'>
<img :src='item.src'>
</div>
</div>
</transition-group>
</div>

Individually color code ToDo cells, not just change everything

I have created a basic to-do list. I want to add the option to color code, similar to Google Keep (this is an exercise). I have tried simply putting one HTML select in, as you can see in my jsfiddle but this changes the background of that entire section.
<p display="none">
<section class="main" style="display: block;" >
<div data-bind="visible:todos().length>0">
<input id="toggle-all" type="checkbox" data-bind="checked:markAll"/>
<label for="toggle-all">Mark all as complete</label>
<br /><br />
<select id="colorOptions" id="toggle-all"></select>
</div>
<ul id="mycell" class="todo-list" data-bind="template:{ name:'item-template',foreach: todos}">
</ul>
</section>
</p>
Question: How do I add an option to individually color code the to-do items?
Ok, I did it using the templates. First I added the selection for the colors inside the template in the li, so that every entry has a selection. Then I added an id for every selection (which is the order variable, the first <select> has the id="0" the second has the id="1" and so on) in order to differentiate them and to make each of them have different functions onchange. I also made the addColorsOptions function which adds the options on every selection when it is created using its unique id.
$(function(){
var Todo = function(id, title, done, order,callback) {
var self = this;
self.id = ko.observable(id);
self.title = ko.observable(title);
self.done = ko.observable(done);
self.order = order;
self.updateCallback = ko.computed(function(){
callback(self);
return true;
});
}
var viewModel = function(){
var self = this;
self.todos = ko.observableArray([]);
self.inputTitle = ko.observable("");
self.doneTodos = ko.observable(0);
self.markAll = ko.observable(false);
self.addOne = function() {
var order = self.todos().length;
var t = new Todo(order, self.inputTitle(),false,order,self.countUpdate);
self.todos.push(t);
self.addColorsOptions(order);
};
self.createOnEnter = function(item,event){
if (event.keyCode == 13 && self.inputTitle()){
self.addOne();
self.inputTitle("");
}else{
return true;
};
}
self.toggleEditMode = function(item,event){
$(event.target).closest('li').toggleClass('editing');
}
self.editOnEnter = function(item,event){
if (event.keyCode == 13 && item.title){
item.updateCallback();
self.toggleEditMode(item,event);
}else{
return true;
};
}
self.markAll.subscribe(function(newValue){
ko.utils.arrayForEach(self.todos(), function(item) {
return item.done(newValue);
});
});
self.countUpdate = function(item){
var doneArray = ko.utils.arrayFilter(self.todos(), function(it) {
return it.done();
});
self.doneTodos(doneArray.length);
return true;
};
self.countDoneText = function(bool){
var cntAll = self.todos().length;
var cnt = (bool ? self.doneTodos() : cntAll - self.doneTodos());
var text = "<span class='count'>" + cnt.toString() + "</span>";
text += (bool ? " completed" : " remaining");
text += (self.doneTodos() > 1 ? " items" : " item");
return text;
}
self.clear = function(){
self.todos.remove(function(item){ return item.done(); });
}
self.addColorsOptions = function(id){
var colors = [{
display: "light yellow",
value: "ffffcc"
}, {
display: "light blue",
value: "ccffff"
}, {
display: "light green",
value: "ccffcc"
}, {
display: "gray",
value: "cccccc"
}, {
display: "white",
value: "ffffff"
}];
var options = ['<option value="">Select color</option>'];
for(var i = 0; i < colors.length; i++){
options.push('<option value="');
options.push(colors[i].value);
options.push('">');
options.push(colors[i].display);
options.push('</option>');
}
$("#" + id).html(options.join('')).change(function(){
var val = $(this).val();
if(val){
$("#" + id).closest('li').css('backgroundColor', '#' + val);
}
});
}
};
ko.applyBindings(new viewModel());
})
body {
font-size: 14px;
background-color: #3c6dc5;
color: #333333;
width: 520px;
margin: 0 auto;
}
#todoapp {
background-color: #3c6dc4;
padding: 20px;
margin-bottom: 40px;
}
#todoapp h1 {
font-size: 36px;
text-align: center;
color:white;
}
#todoapp input[type="text"] {
width: 466px;
font-size: 24px;
line-height: 1.4em;
padding: 6px;
color:#000033;
}
.main {
display: none;
}
.todo-list {
margin: 10px 0;
padding: 0;
list-style: none;
color: #E0E0EF;
}
.todo-list li {
padding: 18px 20px 18px 0;
position: relative;
font-size: 24px;
border-bottom: 1px solid #cccccc;
}
.todo-list li:last-child {
border-bottom: none;
}
.todo-list li .edit {
display: none;
}
.todo-list li.editing {
border-bottom: 1px solid #778899;
}
.todo-list li.editing .view {
display: none;
}
.todo-list li.editing .edit {
display: block;
width: 444px;
padding: 13px 15px 14px 20px;
margin: 0;
}
.todo-list li.done label {
color: #777777;
text-decoration: line-through;
}
.todo-list .destroy {
position: absolute;
right: 5px;
top: 20px;
display: none;
cursor: pointer;
width: 20px;
height: 20px;
}
#todoapp footer {
display: none;
margin: 0 -20px -20px -20px;
overflow: hidden;
color: #555555;
background: #f4fce8;
border-top: 1px solid #ededed;
padding: 0 20px;
line-height: 37px;
}
.todo-count {
float:left;
}
.todo-count .count{
font-weight:bold;
}
#clear-completed {
float: right;
line-height: 20px;
text-decoration: none;
background: rgba(0, 0, 0, 0.1);
color: #555555;
font-size: 11px;
margin-top: 8px;
margin-bottom: 8px;
padding: 0 10px 1px;
cursor: pointer;
}
label{color:white;}
<script src="http://knockoutjs.com/downloads/knockout-2.3.0.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="todoapp">
<header>
<h1>Fun To Do!</h1>
<input id="new-todo" type="text" placeholder="Finish Homework..." data-bind="value:inputTitle,event: { keyup: createOnEnter}"/>
</header>
<section class="main" style="display: block;" >
<div data-bind="visible:todos().length>0">
<input id="toggle-all" type="checkbox" data-bind="checked:markAll"/>
<label for="toggle-all">Mark all as complete</label> <br /><br />
</div>
<ul id="mycell" class="todo-list" data-bind="template:{ name:'item-template',foreach: todos}">
</ul>
</section>
<footer style="display: block;">
<div data-bind="visible:todos().length>0">
<div class="todo-count"><b data-bind="text:todos().length"></b> items left</div>
<!-- ko if: doneTodos() > 0 -->
<a id="clear-completed" data-bind="click:clear">
Clear <span data-bind="html:countDoneText(true)"></span>.
</a>
<!-- /ko -->
<br style="clear:both"/>
</div>
</footer>
</div>
<script type="text/template" id="item-template">
<li data-bind="event:{ dblclick :$root.toggleEditMode},css : {done:done() }">
<div class="view" >
<input class="toggle" type="checkbox" data-bind="checked:done"/>
<label data-bind="text:title"></label>
<a class="destroy"></a>
</div>
<input class="edit" type="text" data-bind="value:title,event: { keyup: $root.editOnEnter}" />
<select data-bind="attr: {'id': id}"></select>
</li>
</script>
You can also see the JSFiddle here.
For any question feel free to comment. I hope this was helpful :)
EDIT
See this JSFiddle for the <input type="color"> addition.
I tested your fiddle code. There is some unwanted code (that i did not find) that fade out your div databind="..... So to remove this problem you can add this at the end of jquery codes before last }); to see some thing like this:
$('div').show(); //adding code
});
Now the select works and you can see the changes in page inspector, But you can not see that here because your ul is empty. To see the result change that ul with an static data ul like this:
<ul id="mycell" class="todo-list">
<li>first</li>
<li>second</li>
</ul>

Double scrollbar with shrinking toolbar

I am working on a photo gallery in AngularJS using Angular Material (run the snippet in fullscreen to see my problem).
var app = angular.module('app', ['ngMaterial']);
app.controller('TitleController', function($scope) {
$scope.title = 'Gallery';
});
app.controller('GalleryCtrl', function($scope, $http, $q, $mdMedia, $mdDialog) {
//https://material.angularjs.org/latest/demo/virtualRepeat
$scope.Images = [],
//add more images
$scope.LoadMore = function() {
for (i = 0; i < 25; i++) {
var randomWidth = Math.round(Math.random() * (800 - 400) + 400);
var randomHeight = Math.round(Math.random() * (800 - 400) + 400);
$scope.Images.push({
src: "http://placehold.it/" + randomWidth + "x" + randomHeight + "/",
id: Math.round(Math.random() * 10000)
});
};
}
$scope.ShowDetails = function(ev, number) {
var useFullScreen = ($mdMedia('sm') || $mdMedia('xs')) && $scope.customFullscreen;
$mdDialog.show({
controller: DialogController,
templateUrl: 'Home/GetInfo?id=' + number,
parent: angular.element(document.body),
targetEvent: ev,
clickOutsideToClose: true,
fullscreen: useFullScreen
})
$scope.$watch(function() {
return $mdMedia('xs') || $mdMedia('sm');
}, function(wantsFullScreen) {
$scope.customFullscreen = (wantsFullScreen === true);
});
};
function DialogController($scope, $mdDialog) {
$scope.hide = function() {
$mdDialog.hide();
};
$scope.cancel = function() {
$mdDialog.cancel();
};
$scope.answer = function(answer) {
$mdDialog.hide(answer);
};
}
//initial loading
$scope.LoadMore();
});
body {
background: #eeeeee;
}
html {
background: #eeeeee;
}
.gridListdemoBasicUsage md-grid-list {
margin: 8px;
}
.gridListdemoBasicUsage .green {
background: #b9f6ca;
}
.gridListdemoBasicUsage md-grid-tile {
transition: all 400ms ease-out 50ms;
}
.responsiveImage {
max-width: 100%;
max-height: 100%;
}
md-content {
background: #eeeeee;
position: relative;
}
.fit {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.toolbardemoScrollShrink .face {
width: 48px;
margin: 16px;
border-radius: 48px;
border: 1px solid #ddd;
}
.md-toolbar-tools {
background-color: #259b24;
}
.dialogdemoBasicUsage #popupContainer {
position: relative;
}
.dialogdemoBasicUsage .footer {
width: 100%;
text-align: center;
margin-left: 20px;
}
.dialogdemoBasicUsage .footer,
.dialogdemoBasicUsage .footer > code {
font-size: 0.8em;
margin-top: 50px;
}
.dialogdemoBasicUsage button {
width: 200px;
}
.dialogdemoBasicUsage div#status {
color: #c60008;
}
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/angular-material/1.0.7/angular-material.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.0/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.0/angular-animate.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.0/angular-aria.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-material/1.0.7/angular-material.min.js"></script>
<body ng-app="app" ng-controller="GalleryCtrl as gc" ng-cloak="" id="popupContainer" class="gridListdemoBasicUsage dialogdemoBasicUsage">
<md-toolbar md-scroll-shrink="" ng-if="true" ng-controller="TitleController">
<div class="md-toolbar-tools">
<h3><span>{{title}}</span></h3>
</div>
</md-toolbar>
<md-content style="height:100vh" />
<md-grid-list md-cols-xs="1" md-cols-sm="2" md-cols-md="4" md-cols-gt-md="6" md-row-height-gt-md="1:1" md-row-height="2:2" md-gutter="12px" md-gutter-gt-sm="8px">
<md-grid-tile ng-click="ShowDetails($event, n.id)" ng-repeat="n in Images" class="green">
<img class="responsiveImage" src="{{n.src}}">
<md-grid-tile-footer>
<h3>Photo number {{n.id}}</h3>
</md-grid-tile-footer>
</md-grid-tile>
</md-grid-list>
<section layout="row" layout-sm="column" layout-align="center center" layout-wrap="">
<md-button class="md-raised md-primary" ng-click="LoadMore()">Primary</md-button>
</section>
</body>
Go fullscreen, scroll to the bottom of the page, and press a button to load more images. The problem I'm having: I am trying to get the toolbar at the top of the screen to disappear when scrolling down, and appear again when scrolling up. However, 2 scrollbars appear, and only the right one affects the toolbar.The left scrollbar actually scrolls all the way down on the page.
My desired situation: only 1 visible scrollbar to scroll down the entire gallery, that also makes the toolbar appear and disappear. How do I do this?
try adding overflow-y: hidden; to the body css rule.

Resources