Web Design Part 3: Fully Functional Horizontal Slider (Useful for Settings)

,

Return

In Case You Missed the Previous Tutorials…


Hello fellow friends! :smile:
The purpose of these tutorial series is to, not only inform, but show you how to replicate certain objects / features found on many websites.


Disclaimers / Knowledge Required :exclamation:

None really :slightly_smiling_face:, just basic scripting knowledge is needed.


Overview :newspaper:

In today’s tutorial, we will be making a slider. Sliders are usually horizontal bars that can be dragged. When the user drags on the slider, it’s “bar” sizes along with it. Sliders are mainly found within web settings. In fact, the Roblox in game volume option in the Esc menu settings is actually considered to be a slider. These always have a minimum and a maximum value.

If you never heard about sliders before, this may not make sense to you. But this video below gives you an idea of what a slider is. In fact, that is the one we will have completed by the end:

Notice that the :

  • slider behaves very similarly to web sliders (the bars still change size if the mouse leaves the slider, only while the mouse is pressed down; and players can simply click to move the slider)
  • slider displays the number underneath (out of a 100)
  • player is able to change the slider value by editing the TextBox
  • slider has no glitches that I have come across so far
  • slider does have constraints as to what is to be accepted by the TextBox (i.e. numbers out of the min-max range and non-number values are rejected)

Setup :hammer_and_wrench:

All you will need for this tutorial is:

  • obviously a ScreenGui (“Slider” in the picture below)
  • a background Frame (“Frame”)
  • a TextButton that shows the range of motion (“Max”)
  • a Frame that will be the actual bar representing the value (“Bar”)
  • a TextBox (“TextControl”)
  • a BoolValue for use later on (“Fire”)
  • most importantly, two Local Scripts (“SliderManager” and “TextManager”)

SliderExplorer


Scripting (“SliderManager”) :scroll:

Slider manager will control the size of “Bar” by physical movement from the mouse. First off, it is essential to define our variables:

local max = script.Parent
local fire = max.Fire
local bar = max.Bar
local player = game.Players.LocalPlayer
local mouse = player:GetMouse()
local uis = game:GetService("UserInputService")
local ap = Vector2.new(max.AbsolutePosition.X, max.AbsolutePosition.Y)
local as = Vector2.new(max.AbsoluteSize.X, max.AbsoluteSize.Y)
local x = mouse.X
local down = false --if MouseButton1 is down or not

We will start off with the easiest of the functions in this script. The first one will control “Bar” depending on where the mouse is pressed down (not dragged). The second and the third ones will be responsible for when the mouse press has ended.

max.MouseButton1Down:Connect(function()
	
	bar.Size = UDim2.new(0, (mouse.X - ap.X), 1, 0) --where ever we click, the bar's size changes to reach the position
	fire.Value = true
	down = true
 	
end)

uis.InputEnded:Connect(function(input, gp)
	
	if input.UserInputType == Enum.UserInputType.MouseButton1 then
		
		down = false
		fire.Value = false
	
	end
	
end)

You: I have a question.

Yes, continue.

Why are we using the “down” variable?

Well, this will let the last function in this script (shown below) knows when the mouse is held down.

Also, why do we need to use UserInputService.InputEnded() instead of GuiButton.MouseButton1Up()?

That is because the latter will only fire if the left mouse button is released within the button. If it was released outside of the button, then it would not fire. The former solves the problem because it fires no matter what the circumstances are. I know that this is very minor, but it is always nice to have a bug-free slider!

Next, we will introduce a more complex function. This one will control the drag.

mouse.Move:Connect(function()
	
	if down == true then --if true, then this represents the mouse being dragged
		
		fire.Value = false 
		fire.Value = true

		if mouse.X < ap.X then --out of bounds (to the left)
			
			bar.Size = UDim2.new(0, 0, 1, 0)
			
		elseif mouse.X > (ap.X + as.X) then --out of bounds (to the right)
			
			bar.Size = UDim2.new(0, as.X, 1, 0)
		
		else --within bounds
			
			bar.Size = UDim2.new(0, (mouse.X - ap.X), 1, 0)
			
		end
			
	end
	
end)

Wow wow! Hold on there. Why are you setting “fire” to false, just to set it to true again?

This will not be easy to explain unless you scroll down to see why. But in a nutshell, this it to fire the Value.Changed() function (so if “fire” is already set to true, it will need to be changed for the function to fire).

That is it for “SliderManager”. We have actually “finished” our slider. It is functional and does the things that a slider does. But the next script is completely optional and controls how manipulate the slider with text and how to show what the slider represents in text.


Scripting (“TextControl”) :scroll:

There are less variables in this script:

local textB = script.Parent
local frame = textB.Parent
local max = frame.Max
local fire = max.Fire
local bar = max.Bar
local as = Vector2.new(max.AbsoluteSize.X, max.AbsoluteSize.Y)

The first function below is to set the text to something between 0 and 100, inclusive.

fire.Changed:Connect(function() --this is where the false-true question's answer is!
	
	if fire.Value == true then
	
		local maxSize = as.X
		local size = bar.Size.X.Offset
		local num = 100 * (size / maxSize) --100 because that is the range we want it to be in
	
		textB.Text = math.floor(num) --to round off any of those good ol' decimals
	
	end
	
end)

Wow! Now that I post the code here, it looks so much simpler than it was for me. I mean that can be because I trying to discover the perfect method!

Anyways, the function below fires when the player has typed in the TextBox and then has finalized the text by leaving the box.

textB.FocusLost:Connect(function() --we used FocusLost because this is basically when the player hits "Enter" to finalize their decision
	
	if typeof(tonumber(textB.Text)) == "number" then -- if text inside of the TextBox is actually a number
			
		local num = tonumber(textB.Text)
				
		if num >= 0 and num <= 100 then
					
			bar.Size = UDim2.new(0, ((num / 100) * as.X), 1, 0)
				
		else
				
			textB.Text = "Min 0, max 100"
			
		end
				
	else -- if the text is NOT a number
				
		textB.Text = "Not valid"
				
	end
			
end)

And there you have it, we are done! This is significantly easier and shorter than Part 2 of this series (the Scrolling-Frame-Features one) If you have followed along correctly, then your slider should work like what the video shows.


Place File (Includes Full Scripts and Previous Tutorial Creations) :file_folder:

UI.rbxl (35.3 KB it’s actually 13.5 TB jk)


Feedback :speech_balloon:

How useful was this tutorial to you?

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

0 voters

Thank you for your feedback and for reading.
Happy developing!

Next Tutorials:

66 Likes

It is not a very good idea to use lots of scripts hidden inside a gui. It becomes hard to edit, repetitive, and ineffecient. It would be a much better idea to have one central local script that handles all of the UI and have it require an object-orientated slider class that could be used to animate lots of these sliders. Other than that your slider is very robust and seems to work well.

11 Likes

For the slider, wouldn’t it be easier to use math.clamp() to make a boundary?

8 Likes

This is still not ideal as it’s much harder for code to share information and to make changes.

3 Likes

How do I make a ball or circle that you click and hold to move the bar?

thxᅟ ᅟ ᅟ ᅟ ᅟ ᅟ ᅟ ᅟ ᅟ ᅟ ᅟ ᅟ ᅟ ᅟ ᅟ

How would I get this to work on mobile??

there is no difference on mobile from pc for this

there is. mobile does not have mousebutton inputs

You would use GuiObject.TouchLongPress

https://developer.roblox.com/en-us/api-reference/event/GuiObject/TouchLongPress

1 Like

Is there any possible way to have a minimum negative value?

Sorry for the bump but I after reading I thought it could be implemented much better. I wrote a more optimised slider module script

slider is a TextButton
bar is a frame

local module = {}

local UserInputService = game:GetService("UserInputService")

local plr = game.Players.LocalPlayer
local mouse = plr:GetMouse()
local sliderTemplate = game.ReplicatedStorage.Slider

function module.slider(pos, size, parent, min, max)
	local slider = sliderTemplate:Clone()
	slider.Position = pos
	slider.Size = size
	slider.Parent = parent
	
	local sliderAbsolutePos = slider.AbsolutePosition
	local sliderAbsoluteSize = slider.AbsoluteSize
	local minXPos = sliderAbsolutePos.X
	local maxXPos = sliderAbsolutePos.X + sliderAbsoluteSize.X
	local range = max - min
	
	local sliderMovedEvent = Instance.new("BindableEvent")
	
	local sliderConnection
	local endInputConnection
	slider.MouseButton1Down:Connect(function()
		local function mouseMoved()
			if mouse.X < minXPos then -- left of slider
				slider.Bar.Size = UDim2.new(0, 0, 1, 0)
			elseif mouse.X > maxXPos then -- right of slider
				slider.Bar.Size = UDim2.new(1, 0, 1, 0)
			else -- within slider bounds
				slider.Bar.Size = UDim2.new(0, mouse.X - minXPos, 1, 0)
			end
			
			sliderMovedEvent:Fire(slider.Bar.AbsoluteSize.X / sliderAbsoluteSize.X * range + min) -- fires a number between min/max, number represents how far slider was dragged
		end
		mouseMoved() -- sets slider pos because player might not move mouse
		sliderConnection = mouse.Move:Connect(mouseMoved)
		
		endInputConnection = UserInputService.InputEnded:Connect(function(input, gp) -- MouseButton1Up only fires when the mouse is within button so use InputEnded
			if input.UserInputType == Enum.UserInputType.MouseButton1 then
				sliderConnection:Disconnect()
				endInputConnection:Disconnect()
			end
		end)
	end)
	
	return sliderMovedEvent.Event
end


return module

Love this but I’m having a problem, instead of making the bar go from left > right, I want the bar to go from right > left.