Vuejs - Use variable for background-image in style template - javascript

I have got a question regarding using a variable within the background-image of the style template. Is this possible at all?
I would like to achieve something like this:
<script>
export default {
name: 'setCss',
computed: {
cssVars() {
var ua = navigator.userAgent.toLowerCase();
var isAndroid = ua.indexOf("android") > -1; //&& ua.indexOf("mobile");
var path = '';
if(isAndroid) {
path = 'img/samsung/';
} else{
path = 'img/iphone/';
}
return {
'--bg-path': path,
}
}
}
};
</script>
and then with this CSS:
<style scoped>
div {
background-image: var(--bg-path) + '/1366x400#2x.jpg';
background-size: 1366px 400px;
}
#media not all,
not all,
only screen and (max-width: 480px) {
div {
background-image: var(--bg-path) + '/480x288.jpg';
background-size: 480px 288px;
}
}
</style>
This will not work but I am wondering if there is a way to do this?

For this to work, you will simply need to pass the computed property cssVars to the component as dynamic style attribute like:
<button :style="cssVars">My button</button>
Here is a working demo:
(here I am passing a hardcoded value for bgColor, but you can assume that being passed as a prop to a component also)
new Vue({
el: "#myApp",
data: {
bgColor: "green"
},
computed: {
cssVars() {
return {
'--bg-color': this.bgColor,
}
}
}
})
button {
background-color: var(--bg-color);
padding:10px 20px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="myApp">
<button :style="cssVars">My button</button>
</div>

You will need to use class/style binding.
In your case, binding style would look something like the following. First, you would need to bind a style with the computed background image (the rest of the styles don't need to be bound, they can remain in your style tag).
<template>
<div :style="{backgroundImage: computedBgImage}"></div>
</template>
...
computed:{
computedBgImage() {
let path;
if (isAndroid) {
path = 'img/samsung/';
} else{
path = 'img/iphone/';
}
return path + '/480x288.jpg'
}
}
...
If you need to use the same image in multiple places, consider defining a class for each image, and binding the class, instead of the style, as explained in the above link.

Related

Javascript detect when computed css property changes

Given some text:
<div>text</div>
I would like to detect when the computed CSS property color changes for this div.
There could be a number of css queries that would change its color, like media queries:
div {
color: black;
#media (prefers-color-scheme: dark) {
color: white;
}
#media screen and (max-width: 992px) {
background-color: blue;
}
}
Or perhaps a class applied to a parent:
div {
}
body.black {
color: white;
}
How can I, using Javascript, observe this change of computed style?
I think we can get part way there at least by using mutation observer on the whole html, that will detect things like change to the attributes of anything, which may or may not influence the color on our to be 'observed' element, and by listening for a resize event which will at least catch common media query changes. This snippet just alerts the color but of course in practice you'll want to remember the previous color (or whatever you are interested in) and check to see if it is different rather than alerting it.
const observed = document.querySelector('.observed');
const html = document.querySelector("html");
let style = window.getComputedStyle(observed);
// NOTE: from MDN: The returned style is a live CSSStyleDeclaration object, which updates automatically when the element's styles are changed.
const observer = new MutationObserver(function(mutations) {
alert('a mutation observed');
mutations.forEach(function(mutation) {
alert(style.color);
});
});
function look() {
alert(style.color);
}
observer.observe(html, {
attributes: true,
subtree: true,
childList: true
});
window.onresize = look;
.observed {
width: 50vmin;
height: 50vmin;
color: red;
}
#media (min-width: 1024px) {
.observed {
color: blue;
}
}
#media (max-width: 700px) {
.observed {
color: gold;
}
<div class="observed">See this color changing.<br> Either click a button or resize the viewport.</div>
<button onclick="observed.style.color = 'purple';">Click for purple</button>
<button onclick="observed.style.color = 'magenta';">Click for magenta</button>
<button onclick="observed.style.color = 'cyan';">Click for cyan</button>
What I don't know is how many other things might influence the setting - I see no way of finding out when the thing is print rather than screen for example. Hopefully someone will be able to fill in any gaps.
I wrote a small program that detects change in a certain CSS property, in getComputedStyle.
Note: Using too many observeChange() may cause performance issues.
let div = document.querySelector("div")
function observeChange(elem, prop, callback) {
var styles = convertObject(getComputedStyle(elem));
setInterval(() => {
let newStyle = convertObject(getComputedStyle(elem));
if (styles[prop] !== newStyle[prop]) { //Check if styles are different or not
callback(prop, styles[prop], newStyle[prop]);
styles = newStyle; //Set new styles to previous object
}
},
500); //You can change the delay
}
//Callback function
function callback(prop, old, newVal) {
console.log(prop + " changed from " + old + " to " + newVal);
}
observeChange(div, "color", callback)
//Convert CSS2Properties object to a normal object
function convertObject(obj) {
let object = {}
for (let i of obj) {
object[i] = obj[i]
}
return object
}
div {
color: green;
}
input:checked+div {
color: red;
}
<input type="checkbox">
<div>Hello World</div>

How to provide a fallback CSS-value within a custom element?

I have a custom web-component which is basically an SVG-Icon:
<custom-icon>
<svg>{svg-stuff}</svg>
</custom-icon>
I want to be able to change it's size by applying CSS like so:
custom-icon {
width: 20px;
}
But I also would like to have a fallback default value when no CSS is applied. However, when I inline some CSS like <custom-icon style="width:15px"> it just overwrites all CSS I apply afterwards. How can I have the default "15px" only apply if there is no custom CSS?
MWE:
class CustomIcon extends HTMLElement {
constructor() {
super();
let size = "100px"
this.style.height = size;
this.style.width = size;
this.style.background = "firebrick"
this.style.display = "block"
}
}
window.customElements.define('custom-icon', CustomIcon);
custom-icon {
--icon-size: 50px;
height: var(--icon-size);
width: var(--icon-size);
}
<custom-icon />
If the content of your custom element is encapsulated in a Shadow DOM, which is a recommended practice, you can use the :host pseudo-class to define a default style.
Then if you define a global style for your custom element it will override the one defined with :host.
customElements.define( 'custom-icon', class extends HTMLElement {
constructor() {
super()
let size = 100
this.attachShadow( { mode: 'open' } )
.innerHTML = `
<style>
:host {
display: inline-block ;
height: ${size}px ;
width: ${size}px ;
background-color: firebrick ;
color: white;
}
</style>
<slot></slot>`
}
} )
custom-icon#i1 {
--icon-size: 50px;
height: var(--icon-size);
width: var(--icon-size);
}
<custom-icon id="i1">sized</custom-icon>
<hr>
<custom-icon>default</custom-icon>
The order is applied according to the cascade.
CSS applied via the style attribute is at the bottom of the cascade. In effect, if you don't specify via the attribute when it falls back to the stylesheet.
So 20px is the fallback for when you don't specify 15px.
You could write your fallback CSS using another rule-set with a less specific selector (although the only thing less specific than a single type selector (like custom-icon) is the universal selector (*) which isn't helpful) so you would need to replace custom-icon with something more specific.
The other option is the take the sledgehammer approach and make every rule in your ruleset !important.
The best option would probably be to fix whatever circumstance might cause your CSS to be missing in the first place.
You can consider data attribute and then use that attribute as a fallback for the custom property.
You can see in the below, that the size will have no effect until we remove the custom property (by setting initial)
class CustomIcon extends HTMLElement {
constructor() {
super();
this.style.height = `var(--icon-size, ${this.getAttribute('size')})`;
this.style.width = `var(--icon-size, ${this.getAttribute('size')})`;
this.style.background = "firebrick"
this.style.display = "block"
}
}
window.customElements.define('custom-icon', CustomIcon);
custom-icon {
--icon-size: 50px;
margin:5px;
}
<custom-icon size="15px"></custom-icon>
<custom-icon size="25px"></custom-icon>
<custom-icon size="2050px"></custom-icon>
<custom-icon size="200px" style="--icon-size:initial"></custom-icon>
Related question to understand the use of initial : CSS custom properties (variables) for box model
Another example where the custom property is not set initially.
class CustomIcon extends HTMLElement {
constructor() {
super();
this.style.height = `var(--icon-size, ${this.getAttribute('size')})`;
this.style.width = `var(--icon-size, ${this.getAttribute('size')})`;
this.style.background = "firebrick"
this.style.display = "block"
}
}
window.customElements.define('custom-icon', CustomIcon);
custom-icon {
margin: 5px;
}
.set {
--icon-size: 50px;
}
<div class="set">
<custom-icon size="15px"></custom-icon>
<custom-icon size="25px"></custom-icon>
<custom-icon size="2050px"></custom-icon>
</div>
<custom-icon size="200px" ></custom-icon>

Can't get overlay to show up in javascript, but could in jquery

I want an overlay to show up when I click a search icon.
I managed to get it working using jQuery. But can't seem to get it working with javascript.
The click event does not seem to be registering and I don't know why.
I've checked all the class names so they match in the same in both the HTML and javascript
Here is the jQuery code that works:
import $ from 'jquery';
class Search {
constructor() {
this.openButton = $('.js-search-trigger');
this.closeButton = $('.search-overlay__close');
this.searchOverlay = $(".search-overlay");
this.events();
}
events() {
this.openButton.on('click', this.openOverlay.bind(this));
this.closeButton.on('click', this.closeOverlay.bind(this));
}
openOverlay() {
this.searchOverlay.addClass("search-overlay--active");
}
closeOverlay() {
this.searchOverlay.removeClass("search-overlay--active");
}
}
export default Search;
Here is the javascript code that does not work:
class Search {
constructor() {
this.openButton = document.querySelector('.js-search-trigger');
this.closeButton = document.querySelector('.search-overlay__close');
this.searchOverlay = document.querySelector('.search-overlay');
this.events();
}
events() {
this.openButton.addEventListener('click', function() {
this.openOverlay.bind(this);
});
this.closeButton.addEventListener('click', function() {
this.closeOverlay.bind(this);
});
}
openOverlay() {
this.searchOverlay.classList.add('search-overlay--active');
}
closeOverlay() {
this.searchOverlay.classList.remove('search-overlay--active');
}
}
export default Search;
No errors were shown in the javascript where the overlay was not showing.
You'll probably want to change your event listeners to use the correct this binding:
this.openButton.addEventListener("click", this.openOverlay.bind(this));
Or use an arrow function to go with your approach - but make sure you actually call the resulting function, as in the above approach the function is passed as a reference and is called. If you removed the additional () from the code below, it would be the same as writing a function out in your code normally - it would be defined, but nothing would happen.
this.openButton.addEventListener("click", () => {
this.openOverlay.bind(this)();
});
jQuery also uses collections of elements rather than single elements, so if you have multiple elements, querySelectorAll and forEach might be in order.
If we are speaking of ecmascript-6 (I see the tag), I would recommend to use arrow function to have this inherited from the above scope, and no bind is needed:
this.openButton.addEventListener('click', () =>
this.openOverlay()
);
The problems with your code are that a) the function creates new scope with its own this; b) bound methods are not being invoked.
Why Search? You're creating an Overlay. Stick with the plan.
No need to bind anything. Use Event.currentTarget if you want to.
No need to handle .open/.close if all you need is a toggle.
And the below should work (as-is) for multiple Overlays. The overlay content is up to you.
class Overlay {
constructor() {
this.toggleButtons = document.querySelectorAll('[data-overlay]');
if (this.toggleButtons.length) this.events();
}
events() {
this.toggleButtons.forEach(el => el.addEventListener('click', this.toggleOverlay));
}
toggleOverlay(ev) {
const btn = ev.currentTarget;
const sel = btn.getAttribute('data-overlay');
const overlay = sel ? document.querySelector(sel) : btn.closest('.overlay');
overlay.classList.toggle('is-active');
}
}
new Overlay();
*{margin:0; box-sizing:border-box;} html,body {height:100%; font:14px/1.4 sans-serif;}
.overlay {
background: rgba(0,0,0,0.8);
color: #fff;
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
padding: 5vw;
transition: opacity 0.4s, visibility 0.4s;
visibility: hidden;
opacity: 0;
}
.overlay.is-active {
opacity: 1;
visibility: visible;
}
<button type="button" data-overlay="#search">OPEN #search</button>
<button type="button" data-overlay="#qa">OPEN #qa</button>
<div class="overlay" id="search">
<button type="button" data-overlay>CLOSE</button>
<h2>SEARCH</h2>
<input type="text" placeholder="Search…">
</div>
<div class="overlay" id="qa">
<button type="button" data-overlay>CLOSE</button>
<h2>Q&A</h2>
<ul><li>Lorem ipsum</li></ul>
</div>
The above is still not perfect, still misses a way to "destroy" events and not re-attach duplicate events to already initialised buttons when trying to target dynamically created ones.
Also, the use of Classes for the above task is absolutely misleading and unnecessary.

CSS style API for Polymer

I came up with the following to modify the style of a webcomponent through:
custom properties in CSS
declaratively in the HTML tag
programatically by changing the properties
My solution uses properties that automatically modify the CSS custom properties.
It looks as follows:
<link rel="import" href="../../bower_components/polymer/polymer.html"/>
<dom-module id="my-bar">
<template>
<style>
:host {
display: block;
box-sizing: border-box;
height: var(--my-bar-bar-height, 50%);
opacity: var(--my-bar-bar-opacity, 0.8);
border: var(--my-bar-bar-border, 1px solid black);
width: 100%;
}
div {
background-color: var(--my-bar-bar-color, blue);
height: 100%;
width: 100%;
margin: 0;
padding: 0;
}
</style>
<div id="bar"></div>
</template>
<script>
Polymer({
is: 'my-bar',
properties: {
barColor: {
type: String,
observer: '_colorChanged'
},
barHeight: {
type: String,
observer: '_heightChanged'
},
barOpacity: {
type: String,
observer: '_opacityChanged'
},
barBorder: {
type: String,
observer: '_borderChanged'
}
},
_heightChanged: function () {
this._styleChanged("barHeight");
},
_colorChanged: function () {
this._styleChanged("barColor");
},
_opacityChanged: function () {
this._styleChanged("barOpacity");
},
_borderChanged: function () {
this._styleChanged("barBorder");
},
_styleChanged: function(name) {
// update the style dynamically, will be something like:
// this.customStyle['--my-bar-bar-color'] = 'red';
this.customStyle[this._getCSSPropertyName(name)] = this[name];
this.updateStyles();
},
_getCSSPropertyName: function(name) {
// retrieves the CSS custom property from the Polymer property name
var ret = "--" + this.is + "-";
var char = "";
for(i = 0; i < name.length; i++)
{
char = name.charAt(i);
if(char >= 'A' && char <= 'Z') {
ret += "-" + char.toLowerCase();
}
else {
ret += char;
}
}
return ret;
}
});
</script>
</dom-module>
Then you can either style in CSS:
my-bar {
--my-bar-bar-color: gray;
}
through HTML:
<my-bar bar-height="20%" bar-opacity="0.1" bar-border="2px solid black"></my-bar>
or JavaScript:
this.$.my-bar.barHeight = "20%;
Adding a new CSS property to the API means adding the following lines:
the property definition
the observer code to pass the property name to _styleChanged()
setting the CSS property to the CSS custom property
I don't think that in Polymer you can specify a variable or constant with the function passed to the observer, so that's why the second point is necessary.
Is there any better way to create a CSS style API for Polymer?
Any improvements or simplifications I could do?
I would recommend against doing this; it may not be compatible with future versions. Hopefully it won't be, since Polymer in many ways is a polyfill until browsers adopt/implement web components on their own.
The purpose of the custom property API is to 'approximate' the CSS variable spec. This is needed since Polymer uses a shady dom, not the real shadow dom which is not widely supported yet.
I recommend sticking to the CSS variable spec for styling.

Apply style "cursor: pointer" to all React components with onClick function

I would like to apply the style cursor:pointer to all React elements that have an onClick function. I know I could do this to every element:
<a onClick={handleClick} style={{cursor:'pointer'}}>Click me</a>
or this:
<a onClick={handleClick} className="someClassWithCursorPointer">Click me</a>
But I'd much rather be able to just do something like this to apply the style to all elements:
<style>
[onclick] {
cursor: pointer;
}
</style>
But that won't work because there's no actual onclick attribute in the rendered HTML of an element when using React's onClick attribute.
Fiddle: https://jsfiddle.net/roj4p1gt/
I am not certain there's a good way to do this automatically without using some sort of mechanism that intercepts the creation of React elements and modifies them (or perhaps source level transforms). For example, using Babel, you could use babel-plugin-react-transform and add a className to all elements with an onClick prop using something along these lines (warning: pseudocode):
export default function addClassNamesToOnClickElements() {
return function wrap(ReactClass) {
const originalRender = ReactClass.prototype.render;
ReactClass.prototype.render = function render() {
var element = originalRender.apply(this, arguments);
return addClickClassNamesToApplicableElements(element);
}
return ReactClass;
}
}
function addClassNamesToApplicableElements(elem) {
if (!elem || typeof elem === "string") return elem;
const children = elem.children;
const modifiedChildren = elem.props.children.map(addClassNamesToApplicableElements);
if (elem.props.onClick) {
const className = elem.props.className || "";
return {
...elem,
props: {
...elem.props,
className: (className + " hasOnClick").trim(),
children: modifiedChildren
}
};
} else {
return {
...elem,
props: {
...elem.props,
children: modifiedChildren
}
}
};
}
Here's a quick example of the second part working: https://bit.ly/1kSFcsg
You can do that easily with css:
a:hover {
cursor:pointer;
}

Categories