如何创建一个输入滑块,可以改变 CSS 的 :before 线性渐变的值?

huangapple go评论86阅读模式
英文:

How to create input slider that changes values of css :before linear-gradient?

问题

我想使用伪元素 :before 在用户通过移动滑块输入来改变的图像上创建渐变效果。我尝试使用以下代码来实现,但目前还没有成功。

var sliderValue = $('#range');

sliderValue.oninput = function(){
    var val1 = this.value;
    var val2 = 100-val1;     
    $('<style> #filter:before{linear-gradient(to top, rgba(255,255,255,1) '+val1+'%,rgba(0,0,0,0) '+val2+'%);}</style>').appendTo("#filter");
};
.slider
{
    -webkit-appearance:none;
    appearance:none;
    width: 100%;
    height: 10px;
    max-width: 400px;
    background-color: #ccc;
    margin: 0;
    padding: 0;
    outline: none;
    border-radius: 10px;
    cursor: pointer;
}
#filter {
    position:relative;
    float: left;
    max-height: 480px; 
}

#filter:before {
    content: "";
    position:absolute;
    top:0;
    left: 0;
    right: 0;
    bottom: 0;
    width:320px;
    height:100%;
    background: linear-gradient(to top, rgba(255,255,255,1) 15%,rgba(0,0,0,0) 22%);
    z-index: 1;
}
<div class="container">
   <input type="range" min="0" max="100" value="0" class="slider" id="range">    
   <div id="filter">  
    <img id="previewImg" src="img/dummy_320x480_ffffff_d002e2.png" alt="Placeholder" style="height: 100%; width:320px;">
   </div>
</div>
<script src="https://code.jquery.com/jquery-3.6.3.min.js" integrity="sha256-pvPw+upLPUjgMXY0G+8O0xUf+/Im1MZjXxxgOcBQBXU=" crossorigin="anonymous"></script>

希望这对你有所帮助。

英文:

I want to create a gradient effect using pseudo-element :before on an image that user would change by moving a slider input. I tried to get to it by following code, but no luck so far

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

var sliderValue = $(&#39;#range&#39;);

sliderValue.oninput = function(){
    var val1 = this.value;
    var val2 = 100-val1;     
    $(&#39;&lt;style&gt; #filter:before{linear-gradient(to top, rgba(255,255,255,1) &#39;+val1+&#39;%,rgba(0,0,0,0) &#39;+val2+&#39;%);}&lt;/style&gt;&#39;).appendTo(&quot;#filter&quot;);
};

<!-- language: lang-css -->

.slider
{
    -webkit-appearance:none;
    appearance:none;
    width: 100%;
    height: 10px;
    max-width: 400px;
    background-color: #ccc;
    margin: 0;
    padding: 0;
    outline: none;
    border-radius: 10px;
    cursor: pointer;
}
#filter {
    position:relative;
    float: left;
    max-height: 480px; 
    
}

#filter:before {
    content: &quot;&quot;;
    position:absolute;
    top:0;
    left: 0;
    right: 0;
    bottom: 0;
    width:320px;
    height:100%;
    background: linear-gradient(to top, rgba(255,255,255,1) 15%,rgba(0,0,0,0) 22%);
    z-index: 1;
}

<!-- language: lang-html -->

&lt;div class=&quot;container&quot;&gt;
   &lt;input type=&quot;range&quot; min=&quot;0&quot; max=&quot;100&quot; value=&quot;0&quot; class=&quot;slider&quot; id=&quot;range&quot;&gt;    
   &lt;div id=&quot;filter&quot;&gt;  
    &lt;img id=&quot;previewImg&quot; src=&quot;img/dummy_320x480_ffffff_d002e2.png&quot; alt=&quot;Placeholder&quot; style=&quot;height: 100%; width:320px;&quot;&gt;
   &lt;/div&gt;
&lt;/div&gt;  
&lt;script src=&quot;https://code.jquery.com/jquery-3.6.3.min.js&quot; integrity=&quot;sha256-pvPw+upLPUjgMXY0G+8O0xUf+/Im1MZjXxxgOcBQBXU=&quot; crossorigin=&quot;anonymous&quot;&gt;&lt;/script&gt;

<!-- end snippet -->

答案1

得分: 1

以下是代码中需要进行修复的部分,以便渐变效果能够正常工作:

  1. 选择输入元素的部分必须使用 jQuery 的 $(...) 函数,而不是 document.getElementById(...)。

  2. 输入元素的 oninput 函数必须与 jQuery 的 input 事件相关联。

  3. 滑块的值应该从 0 到 1 进行标准化,而不是从 0 到 100。

  4. 渐变的 CSS 声明必须校正以使用正确的颜色值表示法(例如:rgba(255,255,255,1) 应该是 rgba(255,255,255,1.0))。

经过这些修复,代码将如下所示:

HTML:

<div class="container">
  <input type="range" min="0" max="100" value="0" class="slider" id="range">    
  <div id="filter">
    <img id="previewImg" src="img/dummy_320x480_ffffff_d002e2.png" alt="Placeholder" style="height: 100%; width: 320px;">
  </div>
</div>

jQuery:

var sliderValue = $('#range');
sliderValue.on('input', function() {
  var val1 = this.value / 100;
  var val2 = 1 - val1;
  $('<style> #filter:before {background: linear-gradient(to top, rgba(255,255,255,1.0) ' + val1*100 + '%, rgba(0,0,0,0) ' + val2*100 + '%);}</style>').appendTo("#filter");
});

CSS:

.slider {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 10px;
  max-width: 400px;
  background-color: #ccc;
  margin: 0;
  padding: 0;
  outline: none;
  border-radius: 10px;
  cursor: pointer;
}

#filter {
  position: relative;
  float: left;
  max-height: 480px; 
}

#filter:before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  width: 320px;
  height: 100%;
  background: linear-gradient(to top, rgba(255,255,255,1.0) 15%, rgba(0,0,0,0) 22%);
  z-index: 1;
}

请记住,<style> 标签用于在 HTML 页面的头部声明 CSS 样式,不应用于在 jQuery 中动态创建样式。在上述代码中,我们使用 appendTo 方法将动态创建的 CSS 样式添加到 #filter 元素中。

英文:

There are a few fixes that need to be made to the code for the gradient to work properly:

  1. The selection of the input element must be done with jQuery's $(...) function, instead of document.getElementById(...).
  2. The input element's oninput function must be linked to jQuery's input event.
  3. The slider value should be normalized from 0 to 1 instead of 0 to 100.
  4. The gradient CSS declaration must be corrected to use the correct notation for the color value (example: rgba(255,255,255,1) should be rgba(255,255,255,1.0)).

With these fixes, the code would look like this:

HTML:

&lt;div class=&quot;container&quot;&gt;
  &lt;input type=&quot;range&quot; min=&quot;0&quot; max=&quot;100&quot; value=&quot;0&quot; class=&quot;slider&quot; id=&quot;range&quot;&gt;    
  &lt;div id=&quot;filter&quot;&gt;
    &lt;img id=&quot;previewImg&quot; src=&quot;img/dummy_320x480_ffffff_d002e2.png&quot; alt=&quot;Placeholder&quot; style=&quot;height: 100%; width:320px;&quot;&gt;
  &lt;/div&gt;
&lt;/div&gt;

jQuery:

var sliderValue = $(&#39;#range&#39;);
sliderValue.on(&#39;input&#39;, function() {
  var val1 = this.value / 100;
  var val2 = 1 - val1;
  $(&#39;&lt;style&gt; #filter:before {background: linear-gradient(to top, rgba(255,255,255,1.0) &#39; + val1*100 + &#39;%, rgba(0,0,0,0) &#39; + val2*100 + &#39;%);}&lt;/style&gt;&#39;).appendTo(&quot;#filter&quot;);
});

CSS:

.slider {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 10px;
  max-width: 400px;
  background-color: #ccc;
  margin: 0;
  padding: 0;
  outline: none;
  border-radius: 10px;
  cursor: pointer;
}

#filter {
  position: relative;
  float: left;
  max-height: 480px; 
}

#filter:before {
  content: &quot;&quot;;
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  width: 320px;
  height: 100%;
  background: linear-gradient(to top, rgba(255,255,255,1.0) 15%, rgba(0,0,0,0) 22%);
  z-index: 1;
}

Remember that the <style> tag is used to declare CSS styles in the header of the HTML page, and should not be used to dynamically create styles in jQuery. In the code above, we used the appendTo method to add the dynamically created CSS style to the #filter element.

答案2

得分: 0

你的代码存在一些问题:

  • 你尝试在每个输入上附加新样式。我已经为样式添加了一个HTML元素,它会在每次输入时更新其内容;
  • 由于某种原因,颜色没有显示出来。我已将 rgba(0,0,0,0) 更改为 rgba(0,0,0,.1),这样你就能看到它了;
  • #filter div在滑块上方,这就是你无法激活它的原因。左浮动已经去掉,现在它可以正常工作。

希望对你有所帮助。

英文:

Some problems with your code:

  • You tried to append the new style on every input. I've added an HTML element for the style which gets it's content updated on every input;
  • The color was not showing for some reason. I've changed the rgba(0,0,0,0) to rgba(0,0,0,.1) so you'll be able to see it
  • The #filter div was on top of the slider, that's why you couldn't activate it. The float left is gone and now it works.

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

var sliderValue = $(&#39;#range&#39;),
    filterStyle = $(&#39;#filter-before&#39;);
    
sliderValue.on(&#39;input&#39;, function() {
  var val1 = this.value;
  var val2 = 100 - val1;
  filterStyle.html(&#39;#filter:before{background: linear-gradient(to top, rgba(255,255,255,1) &#39; + val1 + &#39;%,rgba(0,0,0,.1) &#39; + val2 + &#39;%);}&#39;);
});

<!-- language: lang-css -->

.slider {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 10px;
  max-width: 400px;
  background-color: #ccc;
  margin: 0;
  padding: 0;
  outline: none;
  border-radius: 10px;
  cursor: pointer;
}

#filter {
  position: relative;
  max-height: 480px;
  width: 200px;
  height: 200px;
}

#filter:before {
  content: &quot;&quot;;
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  width: 320px;
  height: 100%;
  background: linear-gradient(to top, rgba(255, 255, 255, 1) 15%, rgba(0, 0, 0, 0) 22%);
  z-index: 1;
}

<!-- language: lang-html -->

&lt;style id=&quot;filter-before&quot;&gt;&lt;/style&gt;
&lt;div class=&quot;container&quot;&gt;
  &lt;input type=&quot;range&quot; min=&quot;0&quot; max=&quot;100&quot; class=&quot;slider&quot; id=&quot;range&quot;&gt;
  &lt;div id=&quot;filter&quot;&gt;
    &lt;img id=&quot;previewImg&quot; src=&quot;img/dummy_320x480_ffffff_d002e2.png&quot; alt=&quot;Placeholder&quot; style=&quot;height: 100%; width:320px;&quot;&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;script src=&quot;https://code.jquery.com/jquery-3.6.3.min.js&quot; integrity=&quot;sha256-pvPw+upLPUjgMXY0G+8O0xUf+/Im1MZjXxxgOcBQBXU=&quot; crossorigin=&quot;anonymous&quot;&gt;&lt;/script&gt;

<!-- end snippet -->

答案3

得分: 0

以下是您要翻译的代码部分:

// simple utility functions to minimise typing:
const D = document,
  get = (selector, context = D) => context.querySelector(selector),
  getAll = (selector, context = D) => [...context.querySelectorAll(selector)];

// creating a new Event to trigger an input-event on the elements, once
// an event-handler is bound:
let inputEvent = new Event('input'),
    // a named arrow function to handle the updates triggered by input-events
    // fired on the <input> elements; this takes a single argument supplied
    // automagically from EventTarget.addEventListener():
    updateGradient = (evt) => {
    // we cache the element to which the function was bound:
    let updated = evt.currentTarget,
        // we cache a reference to the element specified in the
        // <input> element's "data-updates" attribute-value:
        target = get(updated.dataset.updates);

    // we then use CSSStyleDeclaration.setProperty() to set/update the
    // named property - derived from the <input> element's "name"
    // property (from its "name" attribute), to the current value
    // of the changed <input>:
    target.style.setProperty(`--${updated.name}`, updated.value)
  },
  // we get an Array of all <input> elements with the "data-updates"
  // attribute:
  updaters = getAll('input[data-updates]');

// using Array.prototype.forEach() we iterate over the Array
// of nodes using an Arrow function:
updaters.forEach(
  // passing in a reference to the current element/node of
  // the Array of elements/nodes:
  (el) => {
    // here we use EventTarget.addEventListener() to bind the
    // updateGradient() function as the event-handler for the
    // "input" event fired on the <input> elements:
    el.addEventListener('input', updateGradient);

    // here we use EventTarget.dispatchEvent() to fire the 'input'
    // event to trigger the function bound in the previous line:
    el.dispatchEvent(inputEvent);
  });
/* setting a custom property on the :root element, to
   make this property available to the whole document: */
:root {
  --spacing: 1rem;
}

/* a simple CSS reset to remove default margins, paddings
   and to force browsers to use the same sizing algorithm
   for all elements: */
*,
::before,
::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

/* setting a consistent base-font for the document: */
html,
body {
  font-family: system-ui;
  font-size: 16px;
  font-weight: 400;
  /* using CSS logical properties to specify that the
     <html> and <body> elements should take a minimum
     of the full available size of the block axis of
     the document (the height in English): */
  min-block-size: 100%;
  /* specifying the padding on the block-axis: */
  padding-block: var(--spacing);
}

main {
  block-size: 100%;
  border: 1px solid #000;
  display: grid;
  gap: var(--spacing);
  /* CSS logical properties, this sets the size of the
     inline axis (perpendicular to the block-axis) using
     the clamp() function, which sets a preferred size
     of 80% with an absolute minimum size of 30rem and
     a maximum size of 1200px: */
  inline-size: clamp(30rem, 80%, 1200px);
  /* using 'auto' to center the element(s) on its
     inline-axis: */
  margin-inline: auto;
}

form {
  display: grid;
  gap: var(--spacing);
  padding: var(--spacing);
}

label {
  display: flex;
  gap: var(--spacing);
}

.labelText {
  /* allowing the .labelText element to grow to occupy
     all available size on its inline-axis (as defined by
     flex-direction) once siblings have been sized: */
  flex-grow: 1;
  /* aligning the text to the end of its inline-axis: */
  text-align: end;
}

.labelText::after {
  content: ':';
}

label > input {
  /* sizing the <input> to take 70% of the available
     inline space of its parent: */
  flex-basis: 70%;
}

.preview {
  /* defining the custom properties for this element and
     any descendants; note that the --color1 and --color2
     properties are declared using CSS Color Module Level 4
     syntax */
  --color1: rgba(255 255 255 / 1);
  --color2: rgba(0 0 0 / 1);
  --stop: 50;
  /* specifying that the element is sized so its inline-axis
     is twice that of its block axis: */
  aspect-ratio: 2;
  background-image:
    linear-gradient(
      180deg,
      var(--color1, lightskyblue)
      /* we use calc to multiply by 1% in order to convert
         from the unitless --stop property to a percentage,
         in order for it to be used in CSS: */
      calc(var(--stop) * 1%),
      var(--color2, lime)
      calc(var(--stop) * 1%)
    );
}
<main>
  <!-- here we use a <form> element to contain the various <input> elements
       that can modify the gradient; this way if JavaScript fails then the
       page can degrade somewhat gracefully (depending on your back-end
       solutions) and simply be submitted to load a new page with the updated
       settings: -->
  <form action="#" method="post">
    <label>
      <!-- a simple wrapper element for label-text: -->
      <span class="labelText">Color 1</span>
      <!-- an <input> of type "color", to allow the user to modify the color,
           note the 'name' attribute, the starting value and the data-*
           custom property, the attribute-value of which is the selector for
           the element that is updated via this <input>, and the initial
           value of the element is used to initialise the CSS property-values: -->
      <input type="color" name="color1" value="#000000" data-updates=".preview">
    </label>
    <label>
      <span class="labelText">Color 2</span>
      <input type="color" name="color2" value="#ff9900" data-updates=".preview">
    </label>
    <label>
      <span class="labelText">Gradient stop (%)</span>
      <input type="range" name="stop" min="0" max="100" value="25" data-updates=".preview">
    </label>
    <!-- if JavaScript is working then this button is removed via JavaScript
         and no page-loads will be triggered: -->
    <button type="submit">Submit</button>
  </form>
  <div class="preview"></div>
</main>

希望这

英文:

As noted in other answers, there were a number of problems with your initial code, primarily that you were trying to add a &lt;style&gt; element to the document on each occurrence of an input event fired on your sliderValue element. Unfortunately that sliderValue Object was a jQuery Object which had no oninput method.

To bind an event-handler with a jQuery Object the on() method should be used:

sliderValue.on(&#39;&lt;eventName&gt;&#39;, function(){
// functionality
});

However, while other answers have shown improved jQuery approaches I thought I'd take the opportunity to show you how this might be managed via plain JavaScript and CSS custom-properties as follows (with explanatory comments in the code):

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

// simple utility functions to minimise typing:
const D = document,
get = (selector, context = D) =&gt; context.querySelector(selector),
getAll = (selector, context = D) =&gt; [...context.querySelectorAll(selector)];
// creating a new Event to trigger an input-event on the elements, once
// an event-handler is bound:
let inputEvent = new Event(&#39;input&#39;),
// a named arrow function to handle the updates triggered by input-events
// fired on the &lt;input&gt; elements; this takes a single argument supplied
// automagically from EventTarget.addEventListener():
updateGradient = (evt) =&gt; {
// we cache the element to which the function was bound:
let updated = evt.currentTarget,
// we cache a reference to the element specified in the
// &lt;input&gt; element&#39;s &quot;data-updates&quot; attribute-value:
target = get(updated.dataset.updates);
// we then use CSSStyleDeclaration.setProperty() to set/update the
// named property - derived from the &lt;input&gt; element&#39;s &quot;name&quot;
// property (from its &quot;name&quot; attribute), to the current value
// of the changed &lt;input&gt;:
target.style.setProperty(`--${updated.name}`, updated.value)
},
// we get an Array of all &lt;input&gt; elements with the &quot;data-updates&quot;
// attribute:
updaters = getAll(&#39;input[data-updates]&#39;);
// using Array.prototype.forEach() we iterate over the Array
// of nodes using an Arrow function:
updaters.forEach(
// passing in a reference to the current element/node of
// the Array of elements/nodes:
(el) =&gt; {
// here we use EventTarget.addEventListener() to bind the
// updateGradient() function as the event-handler for the
// &quot;input&quot; event fired on the &lt;input&gt; elements:
el.addEventListener(&#39;input&#39;, updateGradient);
// here we use EventTarget.dispatchEvent() to fire the &#39;input&#39;
// event to trigger the function bound in the previous line:
el.dispatchEvent(inputEvent);
});

<!-- language: lang-css -->

/* setting a custom property on the :root element, to
make this property available to the whole document: */
:root {
--spacing: 1rem;
}
/* a simple CSS reset to remove default margins, paddings
and to force browsers to use the same sizing algorithm
for all elements: */
*,
::before,
::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
/* setting a consistent base-font for the document: */
html,
body {
font-family: system-ui;
font-size: 16px;
font-weight: 400;
/* using CSS logical properties to specify that the
&lt;html&gt; and &lt;body&gt; elements should take a minimum
of the full available size of the block axis of
the document (the height in English): */
min-block-size: 100%;
/* specifying the padding on the block-axis: */
padding-block: var(--spacing);
}
main {
block-size: 100%;
border: 1px solid #000;
display: grid;
gap: var(--spacing);
/* CSS logical properties, this sets the size of the
inline axis (perpendicular to the block-axis) using
the clamp() function, which sets a preferred size
of 80% with an absolute minimum size of 30rem and
a maximum size of 1200px: */
inline-size: clamp(30rem, 80%, 1200px);
/* using &#39;auto&#39; to center the element(s) on its
inline-axis: */
margin-inline: auto;
}
form {
display: grid;
gap: var(--spacing);
padding: var(--spacing);
}
label {
display: flex;
gap: var(--spacing);
}
.labelText {
/* allowing the .labelText element to grow to occupy
all available size on its inline-axis (as defined by
flex-direction) once siblings have been sized: */
flex-grow: 1;
/* aligning the text to the end of its inline-axis: */
text-align: end;
}
.labelText::after {
content: &#39;:&#39;;
}
label &gt; input {
/* sizing the &lt;input&gt; to take 70% of the available
inline space of its parent: */
flex-basis: 70%;
}
.preview {
/* defining the custom properties for this element and
any descendants; note that the --color1 and --color2
properties are declared using CSS Color Module Level 4
syntax */
--color1: rgba(255 255 255 / 1);
--color2: rgba(0 0 0 / 1);
--stop: 50;
/* specifying that the element is sized so its inline-axis
is twice that of its block axis: */
aspect-ratio: 2;
background-image:
linear-gradient(
180deg,
var(--color1, lightskyblue)
/* we use calc to multiply by 1% in order to convert
from the unitless --stop property to a percentage,
in order for it to be used in CSS: */
calc(var(--stop) * 1%),
var(--color2, lime)
calc(var(--stop) * 1%)
);
}

<!-- language: lang-html -->

&lt;main&gt;
&lt;!-- here we use a &lt;form&gt; element to contain the various &lt;input&gt; elements
that can modify the gradient; this way if JavaScript fails then the
page can degrade somewhat gracefully (depending on your back-end
solutions) and simply be submitted to load a new page with the updated
settings: --&gt;
&lt;form action=&quot;#&quot; method=&quot;post&quot;&gt;
&lt;label&gt;
&lt;!-- a simple wrapper element for label-text: --&gt;
&lt;span class=&quot;labelText&quot;&gt;Color 1&lt;/span&gt;
&lt;!-- an &lt;input&gt; of type &quot;color&quot;, to allow the user to modify the color,
note the &#39;name&#39; attribute, the starting value and the data-*
custom property, the attribute-value of which is the selector for
the element that is updated via this &lt;input&gt;, and the initial
value of the element is used to initialise the CSS property-values: --&gt;
&lt;input type=&quot;color&quot; name=&quot;color1&quot; value=&quot;#000000&quot; data-updates=&quot;.preview&quot;&gt;
&lt;/label&gt;
&lt;label&gt;
&lt;span class=&quot;labelText&quot;&gt;Color 2&lt;/span&gt;
&lt;input type=&quot;color&quot; name=&quot;color2&quot; value=&quot;#ff9900&quot; data-updates=&quot;.preview&quot;&gt;
&lt;/label&gt;
&lt;label&gt;
&lt;span class=&quot;labelText&quot;&gt;Gradient stop (%)&lt;/span&gt;
&lt;input type=&quot;range&quot; name=&quot;stop&quot; min=&quot;0&quot; max=&quot;100&quot; value=&quot;25&quot; data-updates=&quot;.preview&quot;&gt;
&lt;/label&gt;
&lt;!-- if JavaScript is working then this button is removed via JavaScript
and no page-loads will be triggered: --&gt;
&lt;button type=&quot;submit&quot;&gt;Submit&lt;/button&gt;
&lt;/form&gt;
&lt;div class=&quot;preview&quot;&gt;&lt;/div&gt;
&lt;/main&gt;

<!-- end snippet -->

JS Fiddle demo.

References:

huangapple
  • 本文由 发表于 2023年3月3日 21:02:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/75627418.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定