I'm trying to allow my users to have different themes to choose from however I can't seem to get it working. I've tried multiple different ways, one way is having two different files and i import them based on if the theme is light/dark however once it's imported it stays on the page. Another is within SCSS like this:
$enable-rounded: true;
[theme="dark"] {
$blue: #232c3b;
$body-bg: #262626;
$body-color: white;
}
[theme="light"] {
$body-bg: #ffffff;
$body-color: #000000;
}
#import 'bootstrap/scss/bootstrap.scss';
#import 'bootstrap-vue/src/index.scss';
then in my layouts/default.vue:
import(`~/assets/scss/main.scss`)
export default {
data() {
return {
darkMode: false,
}
},
mounted() {
let bodyElement = document.body;
bodyElement.classList.add("app-background");
let htmlElement = document.documentElement;
let theme = localStorage.getItem("theme");
if (theme === 'dark') {
bodyElement.setAttribute('theme', 'dark')
this.darkMode = true
} else {
bodyElement.setAttribute('theme', 'light');
this.darkMode = false
}
},
watch: {
darkMode: function () {
let htmlElement = document.documentElement;
if (this.darkMode) {
localStorage.setItem("theme", 'dark');
htmlElement.setAttribute('theme', 'dark');
} else {
localStorage.setItem("theme", 'light');
htmlElement.setAttribute('theme', 'light');
}
}
}
}
however nothing happens when I do it this way. I've been trying to figure it out for days and can't seem to get it. It makes me want to just use CSS and avoid SCSS even though I really want to use scss
I think you are importing your scss file in a wrong way. Please try importing it like below out of the script part:
<style lang="scss">
#import '~/assets/scss/main.scss';
</style>
I think you can use this for your project, you can switch into themes or custom on your own
https://color-mode.nuxtjs.org/
Related
For the last couple of days I've been trying several answers, suggestions and tutorials for the problem, but unfortunately non of them did the trick.
The closest one was this:
https://juristr.com/blog/2019/08/dynamically-load-css-angular-cli/
But it uses "extractCss" which has been deprecated since the article has been published.
According to the article:
"styles.js" file should disappear in the Inspector > Network > JS
Clicking the button should add its css file in Inspector > Network > CSS
But neither of these two is happening at the moment.
app.component.ts
const head = this.document.getElementsByTagName('head')[0];
console.log(head);
let themeLink = this.document.getElementById(
'client-theme'
) as HTMLLinkElement;
if (themeLink) {
themeLink.href = styleName;
} else {
const style = this.document.createElement('link');
style.id = 'client-theme';
style.href = `${styleName}`;
head.appendChild(style);
}
}
app.component.html
<head>
</head>
<body>
<button type="button" (click)="loadStyle('client-a-style.css')">STYLE 1</button>
<button type="button" (click)="loadStyle('client-b-style.css')">STYLE 2</button>
</body>
</html>
angular.json
"styles": [
"src/styles.css",
{
"input": "src/client-a-style.css",
"bundleName": "client-a",
"inject": false
},
{
"input": "src/client-b-style.css",
"bundleName": "client-b",
"inject": false
}
These are the main parts of my code.
Hopefully I've explained the problem sufficiently.
Thank you for helping!
You can put your additionals .css in the folder assets (and remove from angular.json)
Then the only change is add the "assets" folder to the href
loadStyle(styleName: string) {
const head = this.document.getElementsByTagName('head')[0];
let themeLink = this.document.getElementById(
'client-theme'
) as HTMLLinkElement;
if (themeLink) {
themeLink.href = `assets/${styleName}`; //<--add assets
} else {
const style = this.document.createElement('link');
style.id = 'client-theme';
style.rel = 'stylesheet';
style.type = 'text/css';
style.href = `assets/${styleName}`; //<--add assets
head.appendChild(style);
}
}
a stackblitz
I think you are missing a property in the link tag, add this to the place you create the link element and it should work.
style.rel = 'stylesheet';
One of possible solution to this task:
import { DOCUMENT } from '#angular/common';
import { Inject, OnDestroy, OnInit, Renderer2 } from '#angular/core';
export class MyComponent implements OnInit, OnDestroy {
private style?: HTMLLinkElement;
constructor(
#Inject(DOCUMENT) private document: Document,
private renderer2: Renderer2,
) {}
public ngOnInit(): void {
const cssPath = '/link/to/style.css';
// Create a link element via Angular's renderer to avoid SSR troubles
this.style = this.renderer2.createElement('link') as HTMLLinkElement;
// Add the style to the head section
this.renderer2.appendChild(this.document.head, this.style);
// Set type of the link item and path to the css file
this.renderer2.setProperty(this.style, 'rel', 'stylesheet');
this.renderer2.setProperty(this.style, 'href', cssPath);
}
public ngOnDestroy(): void {
// Don't forget to remove style after component destroying
this.renderer2.removeChild(this.document.head, this.style);
}
}
If and if your css-files are on the server, so you probably should update your proxy.conf.json file to have access this file from localhost while serve mode is on.
is there a way to change a background-image on conditions?
Im trying to build a weatherapp and I will change the background of a div.
Like:
Weather api returns:
rainy -> change background image to rain.jpg
snow -> change background image to snow.jpg
sunny -> change background to sunny.jpg
etc.
I've tried multiple ways already but nothing worked.
<template>
<div :class="{background_image}"></div>
</template>
<script>
export default {
// ...
data() {
return {
backgroundImage: 'snow'
},
computed:{
background_image : function () {
switch(this.backgroundImage) {
case 'snow':
return 'bg-snow';
case 'rainy':
return 'bg-rainy';
case 'sunny':
return 'bg-sunny';
default:
return 'bg-default';
}
}
},
methods: {
apiCall() {
//api call update background image according to the response
this.backgroundImage = response ; // response contains sunny text
}
},
// ...
}
</script>
<style>
.bg-sunny{
background-image: url('sunny.jpg');
}
.bg-snow{
background-image: url('snow.jpg');
}
.bg-rainy{
background-image: url('rainy.jpg');
}
.bg-default{
background-image: url('default.jpg');
}
</style>
You can achieve this behavior by looking up the image in an object, where you have defined your key and the corresponding image value. In addition, you need to tell webpack to require that media file. That require tells webpack to treat this file as a request.
<template>
<div :style="{ backgroundImage: `url(${weatherTypes[getWeatherType()]})` }"></div>
</template>
<script>
export default {
// ...
data() {
return {
weatherTypes: {
snow: require('some/path/snow.png'),
sun: require('some/path/sun.png'),
// ...
}
}
},
methods: {
getWeatherType() { return 'snow'; },
},
// ...
}
</script>
Reproduction link
Scenario
We followed this tutorial to provide our users a dark and a light theme.
Problem
The browser loads the expected css-File (dark or light). Nevertheless, the styles are not applied to our components.
This happens in <head></head>:
<link rel="stylesheet" type="text/css" href="theme-dark.css">
or
<link rel="stylesheet" type="text/css" href="theme-light.css">
As you can see, the browser correctly switches the two themes.
Code
angular.json
"styles": [
"projects/menu/src/styles.scss",
{
"input": "projects/menu/src/theming/theme-dark.scss",
"bundleName": "theme-dark",
"inject": false
},
{
"input": "projects/menu/src/theming/theme-light.scss",
"bundleName": "theme-light",
"inject": false
}
],
theme-service.ts
private _mainTheme$: BehaviorSubject<string> = new BehaviorSubject(
'theme-light'
);
private _darkMode$: BehaviorSubject<boolean> = new BehaviorSubject(false);
darkMode$: Observable<boolean> = this._darkMode$.asObservable();
private _renderer: Renderer2;
private head: HTMLElement;
private themeLinks: HTMLElement[] = [];
theme$: Observable<[string, boolean]>;
constructor(
rendererFactory: RendererFactory2,
#Inject(DOCUMENT) document: Document
) {
this.head = document.head;
this._renderer = rendererFactory.createRenderer(null, null);
this.theme$ = combineLatest([this._mainTheme$, this._darkMode$]);
this.theme$.subscribe(async ([mainTheme, darkMode]) => {
const cssExt = '.css';
const cssFilename = darkMode
? 'theme-dark' + cssExt
: mainTheme + cssExt;
await this.loadCss(cssFilename);
if (this.themeLinks.length == 2)
this._renderer.removeChild(this.head, this.themeLinks.shift());
});
}
setMainTheme(name: string) {
this._mainTheme$.next(name);
}
setDarkMode(value: boolean) {
this._darkMode$.next(value);
}
private async loadCss(filename: string) {
return new Promise((resolve) => {
const linkEl: HTMLElement = this._renderer.createElement('link');
this._renderer.setAttribute(linkEl, 'rel', 'stylesheet');
this._renderer.setAttribute(linkEl, 'type', 'text/css');
this._renderer.setAttribute(linkEl, 'href', filename);
this._renderer.setProperty(linkEl, 'onload', resolve);
this._renderer.appendChild(this.head, linkEl);
this.themeLinks = [...this.themeLinks, linkEl];
});
}
theme-base.scss
#import '~#angular/material/theming';
#include mat-core();
#mixin theming($theme) {
#include angular-material-theme($theme);
}
theme-dark.scss
#import '~#angular/material/theming';
#import './theme-base';
#include mat-core();
// color palette definitions
$primary: mat-palette($dark-md-mcgpaletteblack);
$accent: mat-palette($md-mcgpalettewhite);
$warn: mat-palette($dark-md-mcgpalettered);
$background:mat-palette($dark-md-mcgpalettebackground);
$confirm: mat-palette($dark-md-mcgpaletteorange);
$theme-dark: mat-dark-theme((color: (primary: $primary,
accent: $accent,
warn: $warn,
background: $background,
confirm: $confirm)));
theme-dark.scss - looks the same
In style.scss we import both themes:
styles.scss
#import 'projects/menu/src/theming/theme-dark.scss';
#import 'projects/menu/src/theming/theme-light.scss';
.theme-dark {
#include angular-material-theme($theme-dark)
}
.theme-light {
#include angular-material-theme($theme-light)
}
and then we use those variables:
styles.scss
body {
background: mat-color($background, default);
}
h1 {
color: mat-color($accent, default);
}
But currently we also import them in our components. I think this is one thing, that is wrong.
A dialog would look like this:
#import 'projects/menu/src/theming/theme-dark.scss';
#import 'projects/menu/src/theming/theme-light.scss';
.theme-dark {
#include angular-material-theme($theme-dark)
}
.theme-light{
#include angular-material-theme($theme-light)
}
.mat-button {
color: mat-color($accent, default);
background-color: mat-color($confirm, default);
}
What I tried:
Removing the import of both themes in every stylesheet. Colors are still influenced by the order of the theme import in style.scss. Dark theme last -> dark theme will be used and the other way round.
Using the same variables for the themes as in the tutorial. But then we had to change of all of the background-color: mat-color($confirm, default);
And so on. Spent lots of time...
We can't figure out how to put the missing parts together. Do we need to use mixins? Or are we missing setting the class dark-theme on the root component?
Thank you very much in advance.
We finally got it working.
Our mistakes:
#include mat-core(); must only be imported once (in theme-base.scss)
#import 'projects/menu/src/theming/theme-dark.scss'; and #import 'projects/menu/src/theming/theme-light.scss'; must only be imported once (in styles.scss). Importing them in all of the components lets bundle size explode.
Set theme-dark class directly in theme-dark.scss
Do not set classes theme-dark and theme-light in styles.scss or any other component theme
Create two wrapper elements for whole application: <div [ngClass]="{ 'theme-dark': darkMode$ | async }"> <div class="mat-app-background"> <router-outlet></router-outlet></div></div>
If components need to access the theme, use mixins (example shown below)
In theme-service.ts, use OverlayContainer to apply styles to material dialogs as well (as described here)
Mixin example:
settings-dialog.component.scss
#import '~#angular/material/theming';
#mixin settings-dialog-theme($theme) {
.settings-dialog {
#include mat-elevation(2);
.mat-button {
background-color: mat-color($confirm);
}
}
}
settings-dialog.component.html
<div mat-dialog-content class="settings-dialog"> Put your content here </div>
Create a component theme as described here
component-themes.scss
#import 'your-path/settings-dialog.component';
#mixin component-themes($theme) {
#include settings-dialog-theme($theme);
}
Import it into your base theme file
theme-base.scss
#import '~#angular/material/theming';
#import './component-themes.scss';
#include mat-core();
#mixin theming($theme) {
#include angular-material-theme($theme);
#include component-themes($theme);
}
I'm using webpack and try to use css modules for my theming, for example:
<style lang="scss" module="theme1">
.header {
color: red;
}
</style>
<style lang="scss" module="theme2">
.header {
color: blue;
}
</style>
However, both .header tags get the same localIdentName from css-loader and because of that the second theme overrides the other everytime.
My loader chain: vue-loader, css-loader, sass-loader
My current localIdentName: '[local]_[hash:base64:5]' (neither path nor name result in anything, I just wish there was some sort of [value] tag.
Apparently it's because custom inject names were broken during the v14 -> v15 upgrade from vue-loader.
Heres the issue on GitHub with more details:
https://github.com/vuejs/vue-loader/issues/1578
Temporary solution (put this in the module options of css-loader):
{
localIdentName: '[module]_[local]_[hash:base64:5]',
getLocalIdent(context, localIdentName, localName) {
const { resourceQuery, resourcePath } = context;
const { index, module } = loaderUtils.parseQuery(resourceQuery);
const selector = loaderUtils.interpolateName(context, localIdentName, {
context: context.rootContext,
content: resourcePath + resourceQuery + localName,
})
.replace(/\[local\]/gi, localName)
.replace(/\[module\]/gi, typeof module === 'boolean' ? '' : module)
.replace(/\[index\]/gi, index)
.replace(new RegExp('[^a-zA-Z0-9\\-_\u00A0-\uFFFF]', 'g'), '-')
.replace(/^((-?[0-9])|--)/, '_$1');
return selector;
},
}
chrome.tabs.insertCSS(tabId, { code : '#import url("custom.css");' });
OR
chrome.tabs.insertCSS(tabId, { file : 'importer.css' });
importer.css:
#import url("custom.css");
a { color:red!important; } /* this rule applied successfully though. */
Doesn't seem to work as expected.
Why doesn't it work and how to make it work?
Edit:
Maybe I am looking at the wrong source code location, but the source code indicates that it uses the regular style sheet parser to parse the injected CSS. In other words, if the #import directive works in regular CSS documents, it should also work in injected CSS.
https://code.google.com/p/chromium/codesearch#chromium/src/extensions/renderer/script_injection.cc&q=script_injection.cc&sq=package:chromium&type=cs&l=312-320
void ScriptInjection::InjectCss(blink::WebLocalFrame* frame) {
std::vector<std::string> css_sources =
injector_->GetCssSources(run_location_);
for (std::vector<std::string>::const_iterator iter = css_sources.begin();
iter != css_sources.end();
++iter) {
frame->document().insertStyleSheet(blink::WebString::fromUTF8(*iter));
}
}
Edit:
Sample code that's not working:
Directory structure:
ext.root
|-- custom.css
|-- custom.css.js
|-- importer.css
|-- manifest.json
manifest.json:
{
"background": {
"scripts": [ "custom.css.js" ],
"persistent": true
},
"manifest_version": 2,
"name": "custom.css",
"version": "1.0",
"web_accessible_resources" : [ "*" ],
"permissions" : [ "webNavigation", "http://*/", "https://*/" ]
}
custom.css.js:
chrome.webNavigation.onCommitted.addListener(function(details) {
console.log('inserting css');
console.log(chrome.runtime.getURL("custom.css"));
chrome.tabs.insertCSS(details.tabId, { file : 'importer.css' });
chrome.tabs.insertCSS(details.tabId, { code : '#import url("custom.css");' });
chrome.tabs.insertCSS(details.tabId, { code : '#import url(custom.css);' });
chrome.tabs.insertCSS(details.tabId, { code : '#import url("' + chrome.runtime.getURL("custom.css") + '");' });
chrome.tabs.insertCSS(details.tabId, { code : '#import url(' + chrome.runtime.getURL("custom.css") + ');' });
chrome.tabs.insertCSS(details.tabId, { code : '#import "custom.css";' });
chrome.tabs.insertCSS(details.tabId, { code : '#import custom.css;' });
chrome.tabs.insertCSS(details.tabId, { code : '#import "' + chrome.runtime.getURL("custom.css") + '";' });
chrome.tabs.insertCSS(details.tabId, { code : '#import ' + chrome.runtime.getURL("custom.css") + ';' });
});
importer.css:
#import "custom.css";
#import "chrome-extension://__MSG_##extension_id__/custom.css";
#import custom.css;
#import chrome-extension://__MSG_##extension_id__/custom.css;
#import url("custom.css");
#import url("chrome-extension://__MSG_##extension_id__/custom.css");
#import url(custom.css);
#import url(chrome-extension://__MSG_##extension_id__/custom.css);
body { background-color: red!important; } /* change page background color to red */
custom.css (Rules in this file are supposed to be applied but not):
body { border: 20px solid red!important; } /* add a 20px border around the body. */
a { background-color: blue!important; } /* change all link's background color to blue. */
If you try to inject such a file into a tab, relative URLs take its origin.
I.e. if you try to inject this into http://example.com/test/index.html, then the above CSS will try to load http://example.com/test/custom.css.
If custom.css is packed in the extension, you should use absolute path obtained from chrome.runtime.getURL():
chrome.tabs.insertCSS(
tabId,
{ code : '#import url("' + chrome.runtime.getURL(custom.css) + '");' }
);
You may try to use the special constant ##extension_id:
#import url("chrome-extension://__MSG_##extension_id__/custom.css");
but I'm not 100% sure it'll work.
Lastly, make sure the imported CSS file is in web_accessible_resources.
#important doesn't work with tabs.insertCSS(). insertCSS seem to be interpreted before injection in the page. How they are interpreted is not documented. For example !important do not works neither.